Example #1
0
def _check_for_user_lockout(original_object):
    """
    Only to be called when the current user is known to have PERMIT_ADMIN_USERS
    permission, checks that the current user hasn't locked themselves out from
    user administration.
    Also checks that the admin user's administration permission has not been
    accidentally revoked.
    If a lockout has occurred, the supplied original object is re-saved and a
    ParameterError is raised.
    """
    user_ids = [get_session_user_id(), 1]
    for user_id in user_ids:
        db_user = data_engine.get_user(user_id=user_id)
        if db_user:
            try:
                # Require user administration
                if not permissions_engine.is_permitted(
                        SystemPermissions.PERMIT_ADMIN_USERS, db_user):
                    raise ParameterError()
                # For the admin user, also require permissions administration
                if user_id == 1 and not permissions_engine.is_permitted(
                        SystemPermissions.PERMIT_ADMIN_PERMISSIONS, db_user):
                    raise ParameterError()
            except ParameterError:
                # Roll back permissions
                data_engine.save_object(original_object)
                permissions_engine.reset()
                # Raise API error
                who = 'the \'admin\' user' if user_id == 1 else 'you'
                raise ParameterError(
                    'This change would lock %s out of administration' % who)
Example #2
0
 def delete(self, folio_id, export_id):
     db_session = data_engine.db_get_session()
     try:
         # Get the portfolio
         folio = data_engine.get_portfolio(folio_id, _db_session=db_session)
         if folio is None:
             raise DoesNotExistError(str(folio_id))
         # Check permissions
         permissions_engine.ensure_portfolio_permitted(
             folio, FolioPermission.ACCESS_EDIT, get_session_user())
         # Get the single portfolio-export
         folio_export = data_engine.get_object(FolioExport,
                                               export_id,
                                               _db_session=db_session)
         if folio_export is None:
             raise DoesNotExistError(str(export_id))
         if folio_export.folio_id != folio_id:
             raise ParameterError(
                 'export ID %d does not belong to portfolio ID %d' %
                 (export_id, folio_id))
         # Delete it and the export files
         delete_portfolio_export(folio_export,
                                 get_session_user(),
                                 'Deleted: ' + folio_export.describe(True),
                                 _db_session=db_session)
         return make_api_success_response()
     finally:
         db_session.close()
Example #3
0
 def get(self, folio_id, export_id=None):
     # Get the portfolio
     folio = data_engine.get_portfolio(folio_id)
     if folio is None:
         raise DoesNotExistError(str(folio_id))
     # Check permissions
     permissions_engine.ensure_portfolio_permitted(
         folio, FolioPermission.ACCESS_VIEW, get_session_user())
     if export_id is None:
         # List portfolio exports
         exports_list = [
             _prep_folioexport_object(folio, fe) for fe in folio.downloads
         ]
         return make_api_success_response(
             object_to_dict_list(exports_list, _omit_fields))
     else:
         # Get a single portfolio-export
         folio_export = data_engine.get_object(FolioExport, export_id)
         if folio_export is None:
             raise DoesNotExistError(str(export_id))
         if folio_export.folio_id != folio_id:
             raise ParameterError(
                 'export ID %d does not belong to portfolio ID %d' %
                 (export_id, folio_id))
         return make_api_success_response(
             object_to_dict(_prep_folioexport_object(folio, folio_export),
                            _omit_fields))
Example #4
0
def imagedetails():
    # Get/check parameters
    try:
        src = request.args.get('src', '')
        validate_string(src, 1, 1024)
    except ValueError as e:
        raise ParameterError(e)

    # v2.6.4 Don't allow this call to populate the database with unsupported files
    supported_file = (
        get_file_extension(src) in image_engine.get_image_formats(supported_only=True)
    )
    if not supported_file and path_exists(src, require_file=True):
        raise ImageError('The file is not a supported image format')

    # Get the image database entry
    db_image = auto_sync_file(src, data_engine, task_engine)
    if not db_image or db_image.status == Image.STATUS_DELETED:
        raise DoesNotExistError(src)

    # Require view permission or file admin
    permissions_engine.ensure_folder_permitted(
        db_image.folder,
        FolderPermission.ACCESS_VIEW,
        get_session_user()
    )

    return make_api_success_response(object_to_dict(
        _prep_image_object(db_image), _omit_fields
    ))
Example #5
0
    def delete(self, permission_id):
        db_session = data_engine.db_get_session()
        db_commit = False
        try:
            fp = data_engine.get_object(FolderPermission,
                                        permission_id,
                                        _db_session=db_session)
            if fp is None:
                raise DoesNotExistError(str(permission_id))
            try:
                data_engine.delete_folder_permission(fp,
                                                     _db_session=db_session,
                                                     _commit=False)
            except ValueError as e:
                raise ParameterError(str(e))

            db_commit = True
            return make_api_success_response()
        finally:
            if db_commit:
                db_session.commit()
                permissions_engine.reset_folder_permissions()
            else:
                db_session.rollback()
            db_session.close()
Example #6
0
def token():
    username = None
    password = None
    # Get credentials - prefer HTTP Basic Auth
    if request.authorization:
        username = request.authorization.username
        password = request.authorization.password
    # Get credentials - but fall back to POST data
    if not username and not password:
        username = request.form.get('username', '')
        password = request.form.get('password', '')
    # Get credentials - ensure no blanks
    if not username:
        raise ParameterError('Username value cannot be blank')
    if not password:
        raise ParameterError('Password value cannot be blank')

    try:
        user = authenticate_user(username, password, data_engine, logger)
    except AuthenticationError as e:
        # Return 500 rather than 401 for authentication runtime errors
        raise Exception(str(e))

    if user is not None:
        if not user.allow_api:
            raise SecurityError('This account is not API enabled')
        elif user.status != User.STATUS_ACTIVE:
            raise SecurityError('This account is disabled')
        else:
            # Success
            http_auth = TimedTokenBasicAuthentication(app)
            return make_api_success_response({
                'token': http_auth.generate_auth_token({'user_id': user.id})
            })

    # Login incorrect
    logger.warning('Incorrect API login for username ' + username)
    # Slow down scripted attacks
    sleep(1)
    raise SecurityError('Incorrect username or password')
Example #7
0
 def post(self):
     # Check permissions! The current user must have permissions admin to create groups.
     permissions_engine.ensure_permitted(
         SystemPermissions.PERMIT_ADMIN_PERMISSIONS, get_session_user())
     params = self._get_validated_object_parameters(request.form)
     if params['group_type'] == Group.GROUP_TYPE_SYSTEM:
         raise ParameterError('System groups cannot be created')
     group = Group(params['name'], params['description'],
                   params['group_type'])
     group.users = []
     self._set_permissions(group, params)
     data_engine.create_group(group)
     return make_api_success_response(object_to_dict(group))
Example #8
0
 def delete(self, user_id):
     user = data_engine.get_user(user_id=user_id)
     if user is None:
         raise DoesNotExistError(str(user_id))
     if user.id == 1:
         raise ParameterError('The \'admin\' user cannot be deleted')
     data_engine.delete_user(user)
     # If this is the current user, log out
     if get_session_user_id() == user_id:
         log_out()
     # Reset session caches
     reset_user_sessions(user)
     return make_api_success_response(object_to_dict(user))
Example #9
0
 def delete(self, template_id):
     permissions_engine.ensure_permitted(
         SystemPermissions.PERMIT_SUPER_USER, get_session_user())
     template_info = data_engine.get_image_template(template_id)
     if template_info is None:
         raise DoesNotExistError(str(template_id))
     db_default_template = data_engine.get_object(Property,
                                                  Property.DEFAULT_TEMPLATE)
     if template_info.name.lower() == db_default_template.value.lower():
         raise ParameterError(
             'The system default template cannot be deleted')
     data_engine.delete_object(template_info)
     image_engine.reset_templates()
     return make_api_success_response()
Example #10
0
 def post(self):
     """ Creates a disk folder """
     params = self._get_validated_parameters(request.form)
     try:
         db_folder = create_folder(params['path'], get_session_user(),
                                   data_engine, permissions_engine, logger)
         # Return a "fresh" object (without relationships loaded) to match PUT, DELETE
         db_folder = data_engine.get_folder(db_folder.id)
         return make_api_success_response(object_to_dict(db_folder))
     except ValueError as e:
         if type(e) is ValueError:
             raise ParameterError(str(e))
         else:
             raise  # Sub-classes of ValueError
Example #11
0
 def delete(self, group_id):
     # Check permissions! The current user must have permissions admin to delete groups.
     permissions_engine.ensure_permitted(
         SystemPermissions.PERMIT_ADMIN_PERMISSIONS, get_session_user())
     group = data_engine.get_group(group_id=group_id, load_users=True)
     if group is None:
         raise DoesNotExistError(str(group_id))
     try:
         data_engine.delete_group(group)
     except ValueError as e:
         raise ParameterError(str(e))
     # Reset permissions and session caches
     reset_user_sessions(group.users)
     permissions_engine.reset()
     return make_api_success_response()
Example #12
0
 def post(self, folio_id):
     db_session = data_engine.db_get_session()
     try:
         # Get the portfolio
         folio = data_engine.get_portfolio(folio_id, _db_session=db_session)
         if folio is None:
             raise DoesNotExistError(str(folio_id))
         # Check permissions
         permissions_engine.ensure_portfolio_permitted(
             folio, FolioPermission.ACCESS_EDIT, get_session_user())
         # Block the export now if it would create an empty zip file
         if len(folio.images) == 0:
             raise ParameterError(
                 'this portfolio is empty and cannot be published')
         # Create a folio-export record and start the export as a background task
         params = self._get_validated_object_parameters(request.form)
         folio_export = FolioExport(folio, params['description'],
                                    params['originals'],
                                    params['image_parameters'],
                                    params['expiry_time'])
         data_engine.add_portfolio_history(folio,
                                           get_session_user(),
                                           FolioHistory.ACTION_PUBLISHED,
                                           folio_export.describe(True),
                                           _db_session=db_session,
                                           _commit=False)
         folio_export = data_engine.save_object(folio_export,
                                                refresh=True,
                                                _db_session=db_session,
                                                _commit=True)
         export_task = task_engine.add_task(
             get_session_user(), 'Export portfolio %d / export %d' %
             (folio.id, folio_export.id), 'export_portfolio', {
                 'export_id': folio_export.id,
                 'ignore_errors': False
             }, Task.PRIORITY_NORMAL, 'info', 'error', 60)
         # Update and return the folio-export record with the task ID
         folio_export.task_id = export_task.id
         data_engine.save_object(folio_export,
                                 _db_session=db_session,
                                 _commit=True)
         return make_api_success_response(object_to_dict(
             _prep_folioexport_object(folio, folio_export),
             _omit_fields + ['portfolio']),
                                          task_accepted=True)
     finally:
         db_session.close()
Example #13
0
 def put(self, image_id):
     """ Moves or renames a file on disk """
     params = self._get_validated_parameters(request.form)
     # Get image data
     db_img = data_engine.get_image(image_id=image_id)
     if not db_img:
         raise DoesNotExistError(str(image_id))
     # Move
     try:
         db_img = move_file(db_img, params['path'], get_session_user(),
                            data_engine, permissions_engine)
     except ValueError as e:
         if type(e) is ValueError:
             raise ParameterError(str(e))
         else:
             raise  # Sub-classes of ValueError
     # Remove cached images for the old path
     image_engine._uncache_image_id(db_img.id)
     # Return updated image
     return make_api_success_response(
         object_to_dict(_prep_image_object(db_img)))
Example #14
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)
Example #15
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)
Example #16
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)