def upload_form(): # Get upload directory options, and convert path templates to actual paths upload_dirs = [ get_upload_directory(i) for i in range(len(app.config['IMAGE_UPLOAD_DIRS'])) ] # Add in whether the user is allowed to upload and view upload_dirs = [( udir[0], udir[1], permissions_engine.is_folder_permitted( udir[1], FolderPermission.ACCESS_UPLOAD, get_session_user(), folder_must_exist=False ), permissions_engine.is_folder_permitted( udir[1], FolderPermission.ACCESS_VIEW, get_session_user(), folder_must_exist=False ) ) for udir in upload_dirs] # Determine which radio button to pre-select dir_idx = -1 to_path = request.args.get('path', None) if not to_path or to_path == os.path.sep: # Default to the first entry that allows upload for (idx, udir) in enumerate(upload_dirs): if udir[2]: dir_idx = idx break else: # Try matching the defined paths to_path = strip_seps(to_path) for (idx, udir) in enumerate(upload_dirs): if strip_seps(udir[1]) == to_path: dir_idx = idx break # If it's a manual path, use it only if it exists manual_path = '' if dir_idx == -1 and to_path and path_exists(to_path, require_directory=True): manual_path = to_path return render_template( 'upload.html', upload_dirs=upload_dirs, sel_radio_num=dir_idx, manual_path=manual_path )
def wrap_is_folder_permitted(folder, folder_access): """ Provides a template function to return whether the current user has a particular access level to a folder. """ return permissions_engine.is_folder_permitted( folder, folder_access, get_session_user() )
def wrap_is_permitted(flag): """ Provides a template function to return whether the current user has been granted a particular system permission. """ return permissions_engine.is_permitted( flag, get_session_user() )
def inject_template_vars(): """ Sets the custom variables that are available inside templates. """ return { 'logged_in': logged_in(), 'user': get_session_user(), 'FolderPermission': FolderPermission, 'SystemPermission': SystemPermissions }
def _check_internal_request(request, session, from_web, require_login, required_permission_flag=None): """ A low-level component implementing request scheme, port, session and optional system permission checking for an "internal" web request. Incorporates _check_port and _check_ssl_request. Returns a Flask redirect or response if there is a problem, otherwise None. """ # Check the port first if app.config['INTERNAL_BROWSING_PORT']: port_response = _check_port(request, app.config['INTERNAL_BROWSING_PORT'], from_web) if port_response: return port_response # Check SSL second, so that if we need to redirect to HTTPS # we know we're already on the correct port number if app.config['INTERNAL_BROWSING_SSL']: ssl_response = _check_ssl_request(request, from_web) if ssl_response: return ssl_response # Check the session is logged in if require_login: if not logged_in(): if from_web: from_path = request.path if len(request.args) > 0: from_path += '?' + url_encode(request.args) # Go to login page, redirecting to original destination on success return redirect(internal_url_for('login', next=from_path)) else: # Return an error return make_api_error_response(AuthenticationError( 'You must be logged in to access this function' )) # Check admin permission if required_permission_flag: try: permissions_engine.ensure_permitted( required_permission_flag, get_session_user() ) except SecurityError as e: # Return an error if from_web: return make_response(str(e), 403) else: return make_api_error_response(e) # OK return None
def log_security_error(error, request): """ Creates an error log entry and returns true if 'error' is a SecurityError, otherwise performs no action and returns false. """ if error and isinstance(error, SecurityError): ip = request.remote_addr if request.remote_addr else '<unknown>' user = get_session_user() logger.error( 'Security error for %s URL %s for user %s from IP %s : %s' % ( request.method.upper(), request.url, user.username if user else '<anonymous>', ip, unicode(error) ) ) return True else: return False
def edit(): # Get parameters src = request.args.get('src', '') embed = request.args.get('embed', '') try: # Check parameters if src == '': raise ValueError('No filename was specified.') db_img = auto_sync_file(src, data_engine, task_engine) if not db_img or db_img.status == Image.STATUS_DELETED: raise DoesNotExistError(src + ' does not exist') # Require edit permission or file admin permissions_engine.ensure_folder_permitted( db_img.folder, FolderPermission.ACCESS_EDIT, get_session_user() ) return render_template( 'details_edit.html', embed=embed, src=src, db_info=db_img ) except Exception as e: log_security_error(e, request) if app.config['DEBUG']: raise return render_template( 'details_edit.html', embed=embed, src=src, err_msg='The file details cannot be viewed: ' + str(e) )
def folder_browse(): from_path = request.args.get('path', '') show_files = request.args.get('show_files', '') embed = request.args.get('embed', '') msg = request.args.get('msg', '') if from_path == '': from_path = os.path.sep db_session = data_engine.db_get_session() db_committed = False try: # This also checks for path existence folder_list = get_directory_listing(from_path, True) # Auto-populate the folders database db_folder = auto_sync_folder( from_path, data_engine, task_engine, _db_session=db_session ) db_session.commit() db_committed = True # Should never happen if db_folder is None: raise DoesNotExistError(from_path) # Require view permission or file admin permissions_engine.ensure_folder_permitted( db_folder, FolderPermission.ACCESS_VIEW, get_session_user() ) return render_template( 'folder_list.html', formats=image_engine.get_image_formats(), embed=embed, msg=msg, name=filepath_filename(from_path), path=from_path, pathsep=os.path.sep, parent_path=filepath_parent(from_path), folder_list=folder_list, show_files=show_files, db_info=db_folder, db_parent_info=db_folder.parent, STATUS_ACTIVE=Folder.STATUS_ACTIVE ) except Exception as e: log_security_error(e, request) if app.config['DEBUG']: raise return render_template( 'folder_list.html', embed=embed, msg=msg, name=filepath_filename(from_path), path=from_path, err_msg='This folder cannot be viewed: ' + str(e) ) finally: try: if not db_committed: db_session.rollback() finally: db_session.close()
def details(): # Get parameters src = request.args.get('src', '') reset = request.args.get('reset', None) src_path = '' try: # Check parameters if src == '': raise ValueError('No filename was specified.') if reset is not None: reset = parse_boolean(reset) file_disk_info = None file_image_info = None file_geo_info = None db_img = None db_history = None db_image_stats = None (src_path, src_filename) = os.path.split(src) # Require view permission or file admin permissions_engine.ensure_folder_permitted( src_path, FolderPermission.ACCESS_VIEW, get_session_user() ) # Get file info from disk file_disk_info = get_file_info(src) if file_disk_info: # Get EXIF info file_image_info = image_engine.get_image_properties(src, True) # Get geo location if we have the relevant profile fields file_geo_info = get_exif_geo_position(file_image_info) # Reset image if requested, then remove the reset from the URL if reset and file_disk_info: image_engine.reset_image(ImageAttrs(src)) return redirect(internal_url_for('details', src=src)) # Get database info db_session = data_engine.db_get_session() db_commit = False try: db_img = auto_sync_file(src, data_engine, task_engine, _db_session=db_session) if db_img: # Trigger lazy load of history db_history = db_img.history # Get stats stats_day = data_engine.summarise_image_stats( datetime.utcnow() - timedelta(days=1), datetime.utcnow(), db_img.id, _db_session=db_session ) stats_month = data_engine.summarise_image_stats( datetime.utcnow() - timedelta(days=30), datetime.utcnow(), db_img.id, _db_session=db_session ) stats_day = stats_day[0] if len(stats_day) > 0 else \ (0, 0, 0, 0, 0, 0, 0, 0) stats_month = stats_month[0] if len(stats_month) > 0 else \ (0, 0, 0, 0, 0, 0, 0, 0) db_image_stats = { 'day': { 'requests': stats_day[1], 'views': stats_day[2], 'cached_views': stats_day[3], 'downloads': stats_day[4], 'bytes': stats_day[5], 'seconds': stats_day[6], 'max_seconds': stats_day[7] }, 'month': { 'requests': stats_month[1], 'views': stats_month[2], 'cached_views': stats_month[3], 'downloads': stats_month[4], 'bytes': stats_month[5], 'seconds': stats_month[6], 'max_seconds': stats_month[7] } } db_commit = True finally: try: if db_commit: db_session.commit() else: db_session.rollback() finally: db_session.close() return render_template( 'details.html', src=src, path=src_path, filename=src_filename, file_info=file_disk_info, image_info=file_image_info, geo_info=file_geo_info, db_info=db_img, db_history=db_history, db_stats=db_image_stats, STATUS_ACTIVE=Image.STATUS_ACTIVE, ACTION_DELETED=ImageHistory.ACTION_DELETED, ACTION_CREATED=ImageHistory.ACTION_CREATED, ACTION_REPLACED=ImageHistory.ACTION_REPLACED, ACTION_EDITED=ImageHistory.ACTION_EDITED, ACTION_MOVED=ImageHistory.ACTION_MOVED, pathsep=os.path.sep, timezone=get_timezone_code() ) except Exception as e: log_security_error(e, request) if app.config['DEBUG']: raise return render_template( 'details.html', src=src, path=src_path, err_msg='This file cannot be viewed: ' + str(e) )
def browse(): from_path = request.args.get('path', '') if from_path == '': from_path = os.path.sep # #2475 Default this in case of error in get_directory_listing() directory_info = DirectoryInfo(from_path) db_session = data_engine.db_get_session() db_committed = False try: directory_info = get_directory_listing(from_path, True) # Auto-populate the folders database db_folder = auto_sync_folder( from_path, data_engine, task_engine, _db_session=db_session ) db_session.commit() db_committed = True if db_folder is not None: # Require view permission or file admin permissions_engine.ensure_folder_permitted( db_folder, FolderPermission.ACCESS_VIEW, get_session_user() ) # Remember last path for the Browse and Upload menus if directory_info.exists() and db_folder: session['last_browse_path'] = from_path return render_template( 'list.html', formats=image_engine.get_image_formats(), pathsep=os.path.sep, timezone=get_timezone_code(), directory_info=directory_info, folder_name=filepath_filename(from_path), db_info=db_folder, db_parent_info=db_folder.parent if db_folder else None, STATUS_ACTIVE=Folder.STATUS_ACTIVE ) except Exception as e: log_security_error(e, request) if app.config['DEBUG']: raise return render_template( 'list.html', directory_info=directory_info, err_msg='This folder cannot be viewed: ' + str(e) ) finally: try: if not db_committed: db_session.rollback() finally: db_session.close()
def image(): logger.debug(request.method + ' ' + request.url) try: logged_in = session_logged_in() allow_uncache = app.config['BENCHMARKING'] or app.config['DEBUG'] args = request.args # Get URL parameters for the image src = args.get('src', '') page = args.get('page', None) iformat = args.get('format', None) template = args.get('tmp', None) width = args.get('width', None) height = args.get('height', None) halign = args.get('halign', None) valign = args.get('valign', None) autosizefit = args.get('autosizefit', None) rotation = args.get('angle', None) flip = args.get('flip', None) top = args.get('top', None) left = args.get('left', None) bottom = args.get('bottom', None) right = args.get('right', None) autocropfit = args.get('autocropfit', None) fill = args.get('fill', None) quality = args.get('quality', None) sharpen = args.get('sharpen', None) ov_src = args.get('overlay', None) ov_size = args.get('ovsize', None) ov_opacity = args.get('ovopacity', None) ov_pos = args.get('ovpos', None) icc_profile = args.get('icc', None) icc_intent = args.get('intent', None) icc_bpc = args.get('bpc', None) colorspace = args.get('colorspace', None) strip = args.get('strip', None) dpi = args.get('dpi', None) tile = args.get('tile', None) # Get URL parameters for handling options attach = args.get('attach', None) xref = args.get('xref', None) stats = args.get('stats', None) # Get protected admin/internal parameters cache = args.get('cache', '1') if logged_in or allow_uncache else '1' recache = args.get('recache', None) if allow_uncache else None # eRez compatibility mode src = erez_params_compat(src) # Tweak strings as necessary and convert non-string parameters # to the correct data types try: # Image options if page is not None: page = parse_int(page) if iformat is not None: iformat = iformat.lower() if template is not None: template = template.lower() if width is not None: width = parse_int(width) if height is not None: height = parse_int(height) if halign is not None: halign = halign.lower() if valign is not None: valign = valign.lower() if autosizefit is not None: autosizefit = parse_boolean(autosizefit) if rotation is not None: rotation = parse_float(rotation) if flip is not None: flip = flip.lower() if top is not None: top = parse_float(top) if left is not None: left = parse_float(left) if bottom is not None: bottom = parse_float(bottom) if right is not None: right = parse_float(right) if autocropfit is not None: autocropfit = parse_boolean(autocropfit) if fill is not None: fill = parse_colour(fill) if quality is not None: quality = parse_int(quality) if sharpen is not None: sharpen = parse_int(sharpen) if ov_size is not None: ov_size = parse_float(ov_size) if ov_pos is not None: ov_pos = ov_pos.lower() if ov_opacity is not None: ov_opacity = parse_float(ov_opacity) if icc_profile is not None: icc_profile = icc_profile.lower() if icc_intent is not None: icc_intent = icc_intent.lower() if icc_bpc is not None: icc_bpc = parse_boolean(icc_bpc) if colorspace is not None: colorspace = colorspace.lower() if strip is not None: strip = parse_boolean(strip) if dpi is not None: dpi = parse_int(dpi) if tile is not None: tile = parse_tile_spec(tile) # Handling options if attach is not None: attach = parse_boolean(attach) if xref is not None: validate_string(xref, 0, 1024) if stats is not None: stats = parse_boolean(stats) # Admin/internal options if cache is not None: cache = parse_boolean(cache) if recache is not None: recache = parse_boolean(recache) except (ValueError, TypeError) as e: raise httpexc.BadRequest(unicode(e)) # Package and validate the parameters try: # #2694 Enforce public image limits - perform easy parameter checks if not logged_in: width, height, autosizefit = _public_image_limits_pre_image_checks( width, height, autosizefit, tile, template ) # Store and normalise all the parameters image_attrs = ImageAttrs(src, -1, page, iformat, template, width, height, halign, valign, rotation, flip, top, left, bottom, right, autocropfit, autosizefit, fill, quality, sharpen, ov_src, ov_size, ov_pos, ov_opacity, icc_profile, icc_intent, icc_bpc, colorspace, strip, dpi, tile) image_engine.finalise_image_attrs(image_attrs) except ValueError as e: raise httpexc.BadRequest(unicode(e)) # Get/create the database ID (from cache, validating path on create) image_id = data_engine.get_or_create_image_id( image_attrs.filename(), return_deleted=False, on_create=on_image_db_create_anon_history ) if (image_id == 0): raise DoesNotExistError() # Deleted elif (image_id < 0): raise DBError('Failed to add image to database') image_attrs.set_database_id(image_id) # Require view permission or file admin permissions_engine.ensure_folder_permitted( image_attrs.folder_path(), FolderPermission.ACCESS_VIEW, get_session_user() ) # Ditto for overlays if ov_src: permissions_engine.ensure_folder_permitted( filepath_parent(ov_src), FolderPermission.ACCESS_VIEW, get_session_user() ) # v1.17 If this is a conditional request with an ETag, see if we can just return a 304 if 'If-None-Match' in request.headers and not recache: etag_valid, modified_time = _etag_is_valid( image_attrs, request.headers['If-None-Match'], False ) if etag_valid: # Success HTTP 304 return make_304_response(image_attrs, False, modified_time) # Get the requested image data image_wrapper = image_engine.get_image( image_attrs, 'refresh' if recache else cache ) if (image_wrapper is None): raise DoesNotExistError() # #2694 Enforce public image limits - check the dimensions # of images that passed the initial parameter checks if not logged_in: try: _public_image_limits_post_image_checks( image_attrs.width(), image_attrs.height(), image_attrs.template(), image_wrapper.data(), image_wrapper.attrs().format() ) except ValueError as e: raise httpexc.BadRequest(unicode(e)) # As for the pre-check # Success HTTP 200 return make_image_response(image_wrapper, False, stats, attach, xref) except httpexc.HTTPException: # Pass through HTTP 4xx and 5xx raise except ServerTooBusyError: logger.warn(u'503 Too busy for ' + request.url) raise httpexc.ServiceUnavailable() except ImageError as e: logger.warn(u'415 Invalid image file \'' + src + '\' : ' + unicode(e)) raise httpexc.UnsupportedMediaType(unicode(e)) except SecurityError as e: if app.config['DEBUG']: raise log_security_error(e, request) raise httpexc.Forbidden() except DoesNotExistError as e: # First time around the ID will be set. Next time around it # won't but we should check whether the disk file now exists. if image_attrs.database_id() > 0 or path_exists(image_attrs.filename(), require_file=True): image_engine.reset_image(image_attrs) logger.warn(u'404 Not found: ' + unicode(e)) raise httpexc.NotFound(unicode(e)) except Exception as e: if app.config['DEBUG']: raise logger.error(u'500 Error for ' + request.url + '\n' + unicode(e)) raise httpexc.InternalServerError(unicode(e))
def original(): logger.debug('GET ' + request.url) try: # Get URL parameters for the image src = request.args.get('src', '') # Get URL parameters for handling options attach = request.args.get('attach', None) xref = request.args.get('xref', None) stats = request.args.get('stats', None) # Validate the parameters try: if attach is not None: attach = parse_boolean(attach) if xref is not None: validate_string(xref, 0, 1024) if stats is not None: stats = parse_boolean(stats) image_attrs = ImageAttrs(src) image_attrs.validate() except ValueError as e: raise httpexc.BadRequest(unicode(e)) # Get/create the database ID (from cache, validating path on create) image_id = data_engine.get_or_create_image_id( image_attrs.filename(), return_deleted=False, on_create=on_image_db_create_anon_history ) if (image_id == 0): raise DoesNotExistError() # Deleted elif (image_id < 0): raise DBError('Failed to add image to database') image_attrs.set_database_id(image_id) # Require download permission or file admin permissions_engine.ensure_folder_permitted( image_attrs.folder_path(), FolderPermission.ACCESS_DOWNLOAD, get_session_user() ) # v1.17 If this is a conditional request with an ETag, see if we can just return a 304 if 'If-None-Match' in request.headers: etag_valid, modified_time = _etag_is_valid( image_attrs, request.headers['If-None-Match'], True ) if etag_valid: # Success HTTP 304 return make_304_response(image_attrs, True, modified_time) # Read the image file image_wrapper = image_engine.get_image_original( image_attrs ) if (image_wrapper is None): raise DoesNotExistError() # Success HTTP 200 return make_image_response(image_wrapper, True, stats, attach, xref) except httpexc.HTTPException: # Pass through HTTP 4xx and 5xx raise except ServerTooBusyError: logger.warn(u'503 Too busy for ' + request.url) raise httpexc.ServiceUnavailable() except ImageError as e: logger.warn(u'415 Invalid image file \'' + src + '\' : ' + unicode(e)) raise httpexc.UnsupportedMediaType(unicode(e)) except SecurityError as e: if app.config['DEBUG']: raise log_security_error(e, request) raise httpexc.Forbidden() except DoesNotExistError as e: # First time around the ID will be set. Next time around it # won't but we should check whether the disk file now exists. if image_attrs.database_id() > 0 or path_exists(image_attrs.filename(), require_file=True): image_engine.reset_image(image_attrs) logger.warn(u'404 Not found: ' + src) raise httpexc.NotFound(src) except Exception as e: if app.config['DEBUG']: raise logger.error(u'500 Error for ' + request.url + '\n' + unicode(e)) raise httpexc.InternalServerError(unicode(e))