def _get_validated_object_parameters(self, data_dict): params = { 'group_id': parse_int(data_dict['group_id']), 'folder_id': parse_int(data_dict['folder_id']), 'access': parse_int(data_dict['access']) } validate_number(params['group_id'], 1, sys.maxint) validate_number(params['folder_id'], 1, sys.maxint) validate_number(params['access'], FolderPermission.ACCESS_NONE, FolderPermission.ACCESS_ALL) return params
def _get_validated_object_parameters(self, data_dict): params = { 'group_id': parse_int(data_dict['group_id']), 'folder_id': parse_int(data_dict['folder_id']), 'access': parse_int(data_dict['access']) } validate_number(params['group_id'], 1, sys.maxsize) validate_number(params['folder_id'], 1, sys.maxsize) validate_number(params['access'], FolderPermission.ACCESS_NONE, FolderPermission.ACCESS_ALL) return params
def trace_permissions(): embed = request.args.get('embed', '') user_id = request.args.get('user', '') folder_path = request.args.get('path', '') if folder_path == '': folder_path = os.path.sep folder = None user = None users = [] user_has_admin = False trace = None err_msg = None db_session = data_engine.db_get_session() try: # Get folder and selected user info # User can be None for an anonymous user user_id = parse_int(user_id) if user_id != 0: user = data_engine.get_user(user_id, _db_session=db_session) if user is None: raise DoesNotExistError('This user no longer exists') folder = data_engine.get_folder(folder_path=folder_path, _db_session=db_session) if folder is None or folder.status == Folder.STATUS_DELETED: raise DoesNotExistError('This folder no longer exists') # Get users list users = data_engine.list_users(status=User.STATUS_ACTIVE, order_field=User.username, _db_session=db_session) # Get the folder+user traced permissions trace = permissions_engine._trace_folder_permissions(folder, user) # Flag on the UI if the user has admin for gdict in trace['groups']: gperms = gdict['group'].permissions if gperms.admin_files or gperms.admin_all: user_has_admin = True break except Exception as e: log_security_error(e, request) err_msg = safe_error_str(e) finally: try: return render_template( 'admin_trace_permissions.html', embed=embed, folder=folder, folder_is_root=folder.is_root() if folder else False, user=user, user_list=users, trace=trace, user_has_admin=user_has_admin, err_msg=err_msg, GROUP_ID_PUBLIC=Group.ID_PUBLIC) finally: db_session.close()
def _get_validated_object_parameters(self, data_dict): params = { 'name': data_dict['name'].strip(), 'description': data_dict['description'], 'group_type': parse_int(data_dict['group_type']), 'access_folios': parse_boolean(data_dict.get('access_folios', '')), 'access_reports': parse_boolean(data_dict.get('access_reports', '')), 'access_admin_users': parse_boolean(data_dict.get('access_admin_users', '')), 'access_admin_files': parse_boolean(data_dict.get('access_admin_files', '')), 'access_admin_folios': parse_boolean(data_dict.get('access_admin_folios', '')), 'access_admin_permissions': parse_boolean(data_dict.get('access_admin_permissions', '')), 'access_admin_all': parse_boolean(data_dict.get('access_admin_all', '')) } validate_string(params['description'], 0, 5 * 1024) validate_number(params['group_type'], Group.GROUP_TYPE_SYSTEM, Group.GROUP_TYPE_LDAP) if params['group_type'] == Group.GROUP_TYPE_LOCAL: validate_string(params['name'], 1, 120) return params
def datafeed_image(): try: # Get parameters image_id = parse_long(request.args.get('id', '')) dt_time_from = parse_iso_datetime(request.args.get('from')) dt_time_to = parse_iso_datetime(request.args.get('to')) data_type = parse_int(request.args.get('data_type', '')) require_full_period = (data_type < 8) if require_full_period: # Stop at 'now' minus the stats gap so we don't return incomplete stats dt_time_limit = datetime.utcnow() - timedelta( minutes=app.config['STATS_FREQUENCY']) if dt_time_to > dt_time_limit: dt_time_to = dt_time_limit # Get stats and convert to chart data results = data_engine.search_image_stats(dt_time_from, dt_time_to, image_id) results = add_zero_stats(dt_time_from, dt_time_to, app.config['STATS_FREQUENCY'], results, ImageStats) data = _db_results_to_flot_data(results, data_type) return make_json_response(200, data=data, first=0 if len(data) == 0 else data[0][0], last=0 if len(data) == 0 else data[-1][0]) except Exception as e: if not log_security_error(e, request): logger.error('Error reading image stats: ' + str(e)) if app.config['DEBUG']: raise return make_json_response(200, data=[], first=0, last=0)
def _get_validated_object_parameters(self, data_dict, adding): params = { 'filename': data_dict.get('filename'), 'index': data_dict.get('index'), 'image_parameters': data_dict.get('image_parameters') } if adding: # Require either image_id or image_src if data_dict.get('image_id') and data_dict.get('image_src'): raise ValueError( 'specify only one of either image_id or image_src') elif data_dict.get('image_id'): params['image_id'] = parse_int(data_dict['image_id']) validate_number(params['image_id'], 1, sys.maxsize) elif data_dict.get('image_src'): params['image_src'] = data_dict['image_src'].strip() validate_string(params['image_src'], 5, 1024) else: raise KeyError('image_id or image_src') # Get or default all the others params['filename'] = params['filename'] or '' params['index'] = parse_int( params['index']) if params['index'] else 0 params['image_parameters'] = params['image_parameters'] or '{}' else: # All parameters optional, if not supplied we leave the values unchanged if params['index']: params['index'] = parse_int(params['index']) if params['filename'] is not None: validate_string(params['filename'], 0, 255) if params['filename']: # Zips only support ASCII filenames, block directory traversal attempts sec_filename = secure_filename(params['filename']) if sec_filename != params['filename']: # We could continue with the secured filename, but the caller won't # expect the filename to change, so be consistent and just fail it raise ValueError('filename not allowed, try: ' + sec_filename) if params['index'] is not None: validate_number(params['index'], -999999, 999999) if params['image_parameters'] is not None: validate_string(params['image_parameters'], 2, 100 * 1024) params['image_parameters'] = _image_params_to_template_dict( params['image_parameters']) return params
def _get_validated_object_parameters(self, data_dict): params = { 'human_id': data_dict.get('human_id', '').strip(), 'name': data_dict['name'], 'description': data_dict['description'], 'internal_access': parse_int(data_dict['internal_access']), 'public_access': parse_int(data_dict['public_access']) } if params['human_id']: validate_string(params['human_id'], 1, 64) if params['human_id'] != secure_url_fragment( params['human_id'], True): raise ValueError( 'human_id is not allowed to contain characters: %<>&.?:/') validate_string(params['name'], 0, 255) validate_string(params['description'], 0, 5 * 1024) # For the first release of portfolios we're limiting access to <= DOWNLOAD validate_number(params['internal_access'], FolioPermission.ACCESS_NONE, FolioPermission.ACCESS_DOWNLOAD) validate_number(params['public_access'], FolioPermission.ACCESS_NONE, FolioPermission.ACCESS_DOWNLOAD) return params
def _get_validated_object_parameters(self, data_dict): params = { 'name': data_dict['name'].strip(), 'description': data_dict['description'], 'group_type': parse_int(data_dict['group_type']), 'access_folios': parse_boolean(data_dict.get('access_folios', '')), 'access_reports': parse_boolean(data_dict.get('access_reports', '')), 'access_admin_users': parse_boolean(data_dict.get('access_admin_users', '')), 'access_admin_files': parse_boolean(data_dict.get('access_admin_files', '')), 'access_admin_folios': parse_boolean(data_dict.get('access_admin_folios', '')), 'access_admin_permissions': parse_boolean(data_dict.get('access_admin_permissions', '')), 'access_admin_all': parse_boolean(data_dict.get('access_admin_all', '')) } validate_string(params['description'], 0, 5 * 1024) validate_number(params['group_type'], Group.GROUP_TYPE_SYSTEM, Group.GROUP_TYPE_LDAP) if params['group_type'] == Group.GROUP_TYPE_LOCAL: validate_string(params['name'], 1, 120) return params
def _get_validated_object_parameters(self, data_dict, require_password): params = { 'first_name': data_dict['first_name'], 'last_name': data_dict['last_name'], 'email': data_dict['email'], 'username': data_dict['username'].strip(), 'password': data_dict.get('password', ''), 'auth_type': parse_int(data_dict['auth_type']), 'allow_api': parse_boolean(data_dict.get('allow_api', '')) } validate_string(params['first_name'], 0, 120) validate_string(params['last_name'], 0, 120) validate_string(params['email'], 0, 120) validate_number(params['auth_type'], User.AUTH_TYPE_PASSWORD, User.AUTH_TYPE_LDAP) if params['auth_type'] != User.AUTH_TYPE_LDAP: validate_string(params['username'], 1, 120) if params['password'] or require_password: validate_string(params['password'], 6, 120) return params
def _get_validated_object_parameters(self, data_dict, require_password): params = { 'first_name': data_dict['first_name'], 'last_name': data_dict['last_name'], 'email': data_dict['email'], 'username': data_dict['username'].strip(), 'password': data_dict.get('password', ''), 'auth_type': parse_int(data_dict['auth_type']), 'allow_api': parse_boolean(data_dict.get('allow_api', '')) } validate_string(params['first_name'], 0, 120) validate_string(params['last_name'], 0, 120) validate_string(params['email'], 0, 120) validate_number(params['auth_type'], User.AUTH_TYPE_PASSWORD, User.AUTH_TYPE_LDAP) if params['auth_type'] != User.AUTH_TYPE_LDAP: validate_string(params['username'], 1, 120) if params['password'] or require_password: validate_string(params['password'], 8, 120) return params
def datafeed_image(): try: # Get parameters image_id = parse_long(request.args.get('id', '')) dt_time_from = parse_iso_datetime(request.args.get('from')) dt_time_to = parse_iso_datetime(request.args.get('to')) data_type = parse_int(request.args.get('data_type', '')) require_full_period = (data_type < 8) if require_full_period: # Stop at 'now' minus the stats gap so we don't return incomplete stats dt_time_limit = datetime.utcnow() - timedelta(minutes=app.config['STATS_FREQUENCY']) if dt_time_to > dt_time_limit: dt_time_to = dt_time_limit # Get stats and convert to chart data results = data_engine.search_image_stats(dt_time_from, dt_time_to, image_id) results = add_zero_stats( dt_time_from, dt_time_to, app.config['STATS_FREQUENCY'], results, ImageStats ) data = _db_results_to_flot_data(results, data_type) return make_json_response( 200, data=data, first=0 if len(data) == 0 else data[0][0], last=0 if len(data) == 0 else data[-1][0] ) except Exception as e: if not log_security_error(e, request): logger.error('Error reading image stats: ' + str(e)) if app.config['DEBUG']: raise return make_json_response( 200, data=[], first=0, last=0 )
def trace_permissions(): embed = request.args.get('embed', '') user_id = request.args.get('user', '') folder_path = request.args.get('path', '') if folder_path == '': folder_path = os.path.sep folder = None user = None users = [] user_has_admin = False trace = None err_msg = None db_session = data_engine.db_get_session() try: # Get folder and selected user info # User can be None for an anonymous user user_id = parse_int(user_id) if user_id != 0: user = data_engine.get_user(user_id, _db_session=db_session) if user is None: raise DoesNotExistError('This user no longer exists') folder = data_engine.get_folder(folder_path=folder_path, _db_session=db_session) if folder is None or folder.status == Folder.STATUS_DELETED: raise DoesNotExistError('This folder no longer exists') # Get users list users = data_engine.list_users( status=User.STATUS_ACTIVE, order_field=User.username, _db_session=db_session ) # Get the folder+user traced permissions trace = permissions_engine._trace_folder_permissions(folder, user) # Flag on the UI if the user has admin for gdict in trace['groups']: gperms = gdict['group'].permissions if gperms.admin_files or gperms.admin_all: user_has_admin = True break except Exception as e: log_security_error(e, request) err_msg = str(e) finally: try: return render_template( 'admin_trace_permissions.html', embed=embed, folder=folder, folder_is_root=folder.is_root() if folder else False, user=user, user_list=users, trace=trace, user_has_admin=user_has_admin, err_msg=err_msg, GROUP_ID_PUBLIC=Group.ID_PUBLIC ) finally: db_session.close()
def _get_validated_object_parameters(self, data_dict): params = {'index': parse_int(data_dict['index'])} validate_number(params['index'], -999999, 999999) return params
def upload(): # Get URL parameters for the upload file_list = request.files.getlist('files') path_index = request.form.get('path_index', '-1') # Index into IMAGE_UPLOAD_DIRS or -1 path = request.form.get('path', '') # Manual path when path_index is -1 overwrite = request.form.get('overwrite') ret_dict = {} try: current_user = get_session_user() assert current_user is not None # Check params path_index = parse_int(path_index) if overwrite != 'rename': overwrite = parse_boolean(overwrite) validate_string(path, 0, 1024) if not path and path_index < 0: raise ValueError('Either path or path_index is required') if len(file_list) < 1: raise ValueError('No files have been attached') if path_index >= 0: # Get a "trusted" pre-defined upload folder # image_engine.put_image() will create it if it doesn't exist _, path = get_upload_directory(path_index) else: # A manually specified folder is "untrusted" and has to exist already if not path_exists(path): raise DoesNotExistError('Path \'' + path + '\' does not exist') # Loop over the upload files put_image_exception = None can_download = None saved_files = [] for wkfile in file_list: original_filepath = wkfile.filename original_filename = filepath_filename(original_filepath) # v2.7.1 added if original_filename: db_image = None try: # Don't allow filenames like "../../../etc/passwd" safe_filename = secure_filename( original_filename, app.config['ALLOW_UNICODE_FILENAMES'] ) # v2.7.1 If we already saved a file as safe_filename during this upload, # override this one to have overwrite=rename overwrite_flag = 'rename' if safe_filename in saved_files else overwrite # Save (this also checks user-folder permissions) _, db_image = image_engine.put_image( current_user, wkfile, path, safe_filename, overwrite_flag ) # v2.7.1 Keep a record of what filenames we used during this upload saved_files.append(safe_filename) except Exception as e: # Save the error to use as our overall return value if put_image_exception is None: put_image_exception = e # This loop failure, add the error info to our return data ret_dict[original_filepath] = { 'error': create_api_error_dict(e, logger) } if db_image: # Calculate download permission once (all files are going to same folder) if can_download is None: can_download = permissions_engine.is_folder_permitted( db_image.folder, FolderPermission.ACCESS_DOWNLOAD, get_session_user() ) # This loop success ret_dict[original_filepath] = object_to_dict( _prep_image_object(db_image, can_download), _omit_fields ) else: logger.warning('Upload received blank filename, ignoring file') # Loop complete. If we had an exception, raise it now. if put_image_exception is not None: raise put_image_exception except Exception as e: # put_image returns ValueError for parameter errors if type(e) is ValueError: e = ParameterError(str(e)) # Attach whatever data we have to return with the error # Caller can then decide whether to continue if some files worked e.api_data = ret_dict raise e finally: # Store the result for the upload_complete page cache_engine.raw_put( 'UPLOAD_API:' + str(current_user.id), ret_dict, expiry_secs=(60 * 60 * 24 * 7) ) # If here, all files were uploaded successfully return make_api_success_response(ret_dict)
def imagelist(): # Check parameters try: from_path = request.args.get('path', '') want_info = parse_boolean(request.args.get('attributes', '')) start = parse_int(request.args.get('start', '0')) limit = parse_int(request.args.get('limit', '1000')) validate_string(from_path, 1, 1024) validate_number(start, 0, 999999999) validate_number(limit, 1, 1000) except ValueError as e: raise ParameterError(e) # Get extra parameters for image URL construction, remove API parameters image_params = request.args.to_dict() image_params.pop('path', None) image_params.pop('attributes', None) image_params.pop('start', None) image_params.pop('limit', None) # Get directory listing directory_info = get_directory_listing(from_path, False, 2, start, limit) if not directory_info.exists(): raise DoesNotExistError('Invalid path') ret_list = [] db_session = data_engine.db_get_session() db_commit = False try: # Auto-populate the folders database db_folder = auto_sync_folder( from_path, data_engine, task_engine, _db_session=db_session ) db_session.commit() # Require view permission or file admin permissions_engine.ensure_folder_permitted( db_folder, FolderPermission.ACCESS_VIEW, get_session_user() ) # Get download permission in case we need to return it later can_download = permissions_engine.is_folder_permitted( db_folder, FolderPermission.ACCESS_DOWNLOAD, get_session_user() ) # Create the response file_list = directory_info.contents() supported_img_types = image_engine.get_image_formats(supported_only=True) base_folder = add_sep(directory_info.name()) for f in file_list: # v2.6.4 Return unsupported files too. If you want to reverse this change, # the filtering needs to be elsewhere for 'start' and 'limit' to work properly supported_file = get_file_extension(f['filename']) in supported_img_types file_path = base_folder + f['filename'] if want_info: # Need to return the database fields too if supported_file: db_entry = auto_sync_existing_file( file_path, data_engine, task_engine, burst_pdf=False, # Don't burst a PDF just by finding it here _db_session=db_session ) db_entry = _prep_image_object(db_entry, can_download, **image_params) else: db_entry = _prep_blank_image_object() db_entry.filename = f['filename'] db_entry.supported = False # Return images in full (standard) image dict format entry = object_to_dict(db_entry, _omit_fields) else: # Return images in short dict format entry = { 'filename': f['filename'], 'supported': supported_file, 'url': (external_url_for('image', src=file_path, **image_params) if supported_file else '') } ret_list.append(entry) db_commit = True finally: try: if db_commit: db_session.commit() else: db_session.rollback() finally: db_session.close() return make_api_success_response(ret_list)
def imagelist(): # Check parameters try: from_path = request.args.get('path', '') want_info = parse_boolean(request.args.get('attributes', '')) limit = parse_int(request.args.get('limit', '1000')) validate_string(from_path, 1, 1024) except ValueError as e: raise ParameterError(e) # Get extra parameters for image URL construction image_params = request.args.to_dict() if 'path' in image_params: del image_params['path'] if 'attributes' in image_params: del image_params['attributes'] if 'limit' in image_params: del image_params['limit'] # Get directory listing directory_info = get_directory_listing(from_path, False, limit) if not directory_info.exists(): raise DoesNotExistError('Invalid path') ret_list = [] db_session = data_engine.db_get_session() db_commit = False try: # Auto-populate the folders database db_folder = auto_sync_folder( from_path, data_engine, task_engine, _db_session=db_session ) db_session.commit() # Require view permission or file admin permissions_engine.ensure_folder_permitted( db_folder, FolderPermission.ACCESS_VIEW, get_session_user() ) # Create the response file_list = directory_info.contents() img_types = image_engine.get_image_formats() base_folder = add_sep(directory_info.name()) for f in file_list: # Filter out non-images if get_file_extension(f['filename']) in img_types: entry_path = base_folder + f['filename'] entry = { 'filename': f['filename'], 'url': external_url_for('image', src=entry_path, **image_params) } if want_info: db_entry = auto_sync_existing_file( entry_path, data_engine, task_engine, burst_pdf=False, # Don't burst a PDF just by finding it here _db_session=db_session ) entry['id'] = db_entry.id if db_entry else 0 entry['folder_id'] = db_entry.folder_id if db_entry else 0 entry['title'] = db_entry.title if db_entry else '' entry['description'] = db_entry.description if db_entry else '' entry['width'] = db_entry.width if db_entry else 0 entry['height'] = db_entry.height if db_entry else 0 ret_list.append(entry) db_commit = True finally: try: if db_commit: db_session.commit() else: db_session.rollback() finally: db_session.close() return make_api_success_response(ret_list)
def _get_validated_object_parameters(self, data_dict): params = {'user_id': parse_int(data_dict['user_id'])} validate_number(params['user_id'], 1, sys.maxsize) return params
def topten(): # Get parameters days = request.args.get('days', '1') limit = request.args.get('number', '10') data_type = request.args.get('data_type', '2') try: results = [] db_session = data_engine.db_get_session() try: # Convert params to ints days = parse_int(days) limit = parse_int(limit) data_type = parse_int(data_type) # Set options if days < 1: days = 1 if days > 30: days = 30 if limit < 10: limit = 10 if limit > 100: limit = 100 if data_type == 1: order = '-total_requests' elif data_type == 2: order = '-total_views' elif data_type == 3: order = '-total_cached_views' elif data_type == 4: order = '-total_downloads' elif data_type == 5: order = '-total_bytes' elif data_type == 6: order = '-total_seconds' elif data_type == 7: order = '-max_seconds' else: raise ValueError('Invalid data_type %d' % data_type) # Get initial stats top_stats = data_engine.summarise_image_stats( datetime.utcnow() - timedelta(days=days), datetime.utcnow(), limit=limit, order_by=order, _db_session=db_session) # Convert stats list to an image list for result in top_stats: db_image = data_engine.get_image(image_id=result[0], _db_session=db_session) if db_image: results.append({ 'id': db_image.id, 'src': db_image.src, 'requests': result[1], 'views': result[2], 'cached_views': result[3], 'downloads': result[4], 'bytes': result[5], 'seconds': result[6], 'max_seconds': result[7] }) finally: db_session.close() return render_template('reports_topten.html', days=days, data_type=data_type, number=limit, results=results) except Exception as e: log_security_error(e, request) if app.config['DEBUG']: raise raise InternalServerError(safe_error_str(e))
def _get_validated_object_parameters(self, data_dict): params = {'user_id': parse_int(data_dict['user_id'])} validate_number(params['user_id'], 1, sys.maxint) return params
def upload(): # Get URL parameters for the upload file_list = request.files.getlist('files') path_index = request.form.get('path_index', '-1') # Index into IMAGE_UPLOAD_DIRS or -1 path = request.form.get('path', '') # Manual path when path_index is -1 overwrite = request.form.get('overwrite') ret_dict = {} try: path_index = parse_int(path_index) overwrite = parse_boolean(overwrite) validate_string(path, 0, 1024) current_user = get_session_user() assert current_user is not None put_image_exception = None can_download = None for wkfile in file_list: original_filename = wkfile.filename if original_filename: db_image = None try: # Save (also checks user-folder permissions) _, db_image = image_engine.put_image( current_user, wkfile, secure_filename( original_filename, app.config['ALLOW_UNICODE_FILENAMES'] ), path_index, path, overwrite ) except Exception as e: # Save the error to use as our overall return value if put_image_exception is None: put_image_exception = e # This loop failure, add the error info to our return data ret_dict[original_filename] = {'error': create_api_error_dict(e)} if db_image: # Calculate download permission once (all files are going to same folder) if can_download is None: can_download = permissions_engine.is_folder_permitted( db_image.folder, FolderPermission.ACCESS_DOWNLOAD, get_session_user() ) # This loop success ret_dict[original_filename] = _image_dict(db_image, can_download) # Loop complete. If we had an exception, raise it now. if put_image_exception is not None: raise put_image_exception except Exception as e: # put_image returns ValueError for parameter errors if type(e) is ValueError: e = ParameterError(unicode(e)) # Attach whatever data we have to return with the error # Caller can then decide whether to continue if some files worked e.api_data = ret_dict raise e finally: # Store the result for the upload_complete page cache_engine.raw_put( 'UPLOAD_API:' + str(current_user.id), ret_dict, expiry_secs=(60 * 60 * 24 * 7), integrity_check=True ) # If here, all files were uploaded successfully return make_api_success_response(ret_dict)
def topten(): # Get parameters days = request.args.get('days', '1') limit = request.args.get('number', '10') data_type = request.args.get('data_type', '2') try: results = [] db_session = data_engine.db_get_session() try: # Convert params to ints days = parse_int(days) limit = parse_int(limit) data_type = parse_int(data_type) # Set options if days < 1: days = 1 if days > 30: days = 30 if limit < 10: limit = 10 if limit > 100: limit = 100 if data_type == 1: order = '-total_requests' elif data_type == 2: order = '-total_views' elif data_type == 3: order = '-total_cached_views' elif data_type == 4: order = '-total_downloads' elif data_type == 5: order = '-total_bytes' elif data_type == 6: order = '-total_seconds' elif data_type == 7: order = '-max_seconds' else: raise ValueError('Invalid data_type %d' % data_type) # Get initial stats top_stats = data_engine.summarise_image_stats( datetime.utcnow() - timedelta(days=days), datetime.utcnow(), limit=limit, order_by=order, _db_session=db_session ) # Convert stats list to an image list for result in top_stats: db_image = data_engine.get_image(image_id=result[0], _db_session=db_session) if db_image: results.append({ 'id': db_image.id, 'src': db_image.src, 'requests': result[1], 'views': result[2], 'cached_views': result[3], 'downloads': result[4], 'bytes': result[5], 'seconds': result[6], 'max_seconds': result[7] }) finally: db_session.close() return render_template( 'reports_topten.html', days=days, data_type=data_type, number=limit, results=results ) except Exception as e: log_security_error(e, request) if app.config['DEBUG']: raise raise InternalServerError(str(e))