コード例 #1
0
ファイル: views_pages.py プロジェクト: quru/qis
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
    )
コード例 #2
0
ファイル: views_util.py プロジェクト: quru/qis
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()
    )
コード例 #3
0
ファイル: views_util.py プロジェクト: quru/qis
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()
    )
コード例 #4
0
ファイル: views_util.py プロジェクト: quru/qis
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
    }
コード例 #5
0
ファイル: flask_util.py プロジェクト: quru/qis
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
コード例 #6
0
ファイル: views_util.py プロジェクト: quru/qis
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
コード例 #7
0
ファイル: views_pages.py プロジェクト: quru/qis
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)
        )
コード例 #8
0
ファイル: views_pages.py プロジェクト: quru/qis
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()
コード例 #9
0
ファイル: views_pages.py プロジェクト: quru/qis
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)
        )
コード例 #10
0
ファイル: views_pages.py プロジェクト: quru/qis
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()
コード例 #11
0
ファイル: views.py プロジェクト: quru/qis
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))
コード例 #12
0
ファイル: views.py プロジェクト: quru/qis
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))