Esempio n. 1
0
def _prep_image_object(image, can_download=None, **url_params):
    """
    Modifies an Image object to add calculated fields.
    This provides the common data dictionary for the file admin, image admin,
    image details, upload, and directory listing (with detail) APIs.

    If the download permission is None, it is calculated for the image's folder
    and the current user. The permissions engine returns this from cache when
    possible, but it is more efficient to pass in the permission if it is already
    known, or when handling many images in the same folder.

    If any url_params are provided (as kwargs), these are included in the
    generated image 'url' attribute.
    """
    if can_download is None:
        can_download = permissions_engine.is_folder_permitted(
            image.folder, FolderPermission.ACCESS_DOWNLOAD, get_session_user())
    image.url = external_url_for('image', src=image.src, **url_params)
    image.download = can_download
    image.filename = filepath_filename(image.src)
    # Unsupported files shouldn't be in the database but it can happen if
    # support is removed for a file type that was once enabled
    image.supported = (get_file_extension(image.filename)
                       in image_engine.get_image_formats(supported_only=True))
    return image
Esempio n. 2
0
def _image_dict(db_image, can_download=None):
    """
    Returns the common data dictionary for the imagedetails and upload APIs.
    Provide an Image data model object and optionally the pre-calculated
    boolean download permission. If the download permission is None,
    it is calculated for the image's folder and the current user.
    """
    if can_download is None:
        can_download = permissions_engine.is_folder_permitted(
            db_image.folder,
            FolderPermission.ACCESS_DOWNLOAD,
            get_session_user()
        )
    return {
        'src': db_image.src,
        'url': external_url_for('image', src=db_image.src),
        'id': db_image.id,
        'folder_id': db_image.folder_id,
        'title': db_image.title,
        'description': db_image.description,
        'width': db_image.width,
        'height': db_image.height,
        'download': can_download
    }
Esempio n. 3
0
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)
Esempio n. 4
0
def portfolio_view(human_id):
    try:
        # Find the portfolio
        folio = data_engine.get_portfolio(human_id=human_id, load_images=True)
        if not folio:
            raise DoesNotExistError('Portfolio \'%s\' does not exist' % human_id)

        # Ensure that the user has permission to view the portfolio
        user = get_session_user()
        permissions_engine.ensure_portfolio_permitted(
            folio,
            FolioPermission.ACCESS_VIEW,
            user
        )

        # Filter out images that the user can't view
        # so that we don't get broken images in the UI
        checked_folders = {}  # cache folders already checked
        folio_images_1 = folio.images
        folio_images_2 = []
        for fol_img in folio_images_1:
            folder_path = fol_img.image.folder.path
            if folder_path in checked_folders:
                folio_images_2.append(fol_img)
            elif permissions_engine.is_folder_permitted(
                folder_path,
                FolderPermission.ACCESS_VIEW,
                user,
                folder_must_exist=False  # though it should exist!
            ):
                checked_folders[folder_path] = True
                folio_images_2.append(fol_img)

        # Replace the original image list with the filtered one
        folio.images = folio_images_2

        # Generate the image viewing URLs, including any portfolio-specific changes
        web_view_params = {
            'format': 'jpg',
            'colorspace': 'srgb'
        }
        sizing_view_params = {
            'width': 800,
            'height': 800,
            'size_fit': True
        }
        pre_sized_images = [
            fol_img for fol_img in folio.images if fol_img.parameters and (
                ('width' in fol_img.parameters and fol_img.parameters['width']['value']) or
                ('height' in fol_img.parameters and fol_img.parameters['height']['value'])
            )
        ]
        for fol_img in folio.images:
            image_attrs = get_portfolio_image_attrs(fol_img, False, False, False)
            image_attrs.apply_dict(web_view_params, True, False, False)
            if len(pre_sized_images) == 0:
                image_attrs.apply_dict(sizing_view_params, True, False, False)
            # Here we normalise the attrs only after everything has been applied
            image_attrs.normalise_values()
            fol_img.url = url_for_image_attrs(image_attrs)

        return render_template(
            'portfolio_view.html',
            title=folio.name,
            folio=folio,
            removed_count=(len(folio_images_1) - len(folio_images_2))
        )
    except Exception as e:
        # Although this isn't a JSON API, we're still using it like a viewing API,
        # so get the correct HTTP status code to return. create_api_error_dict() also
        # logs security errors so we don't need to do that separately here.
        error_dict = create_api_error_dict(e, logger)
        if app.config['DEBUG']:
            raise
        return render_template(
            'portfolio_view.html',
            title='Portfolio',
            err_msg='This portfolio cannot be viewed: ' + safe_error_str(e)
        ), error_dict['status']
Esempio n. 5
0
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)
Esempio n. 6
0
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)