def get(self, folio_id, image_id=None): if image_id is None: # List images in the portfolio folio = data_engine.get_portfolio(folio_id, load_images=True) if folio is None: raise DoesNotExistError(str(folio_id)) # Check permissions permissions_engine.ensure_portfolio_permitted( folio, FolioPermission.ACCESS_VIEW, get_session_user()) image_list = [_prep_folioimage_object(fi) for fi in folio.images] return make_api_success_response( object_to_dict_list(image_list, _omit_fields)) else: # Get a single portfolio-image db_session = data_engine.db_get_session() try: folio_image = data_engine.get_portfolio_image( AttrObject(id=folio_id), AttrObject(id=image_id), _db_session=db_session) if folio_image is None: raise DoesNotExistError( str(folio_id) + '/' + str(image_id)) # Check permissions permissions_engine.ensure_portfolio_permitted( folio_image.portfolio, FolioPermission.ACCESS_VIEW, get_session_user()) return make_api_success_response( object_to_dict(_prep_folioimage_object(folio_image), _omit_fields + ['portfolio'])) finally: db_session.close()
def post(self, function_name): """ Launches a system task """ # Validate function name if getattr(tasks, function_name, None) is None: raise DoesNotExistError(function_name) # Requires super user permissions_engine.ensure_permitted(SystemPermissions.PERMIT_SUPER_USER, get_session_user()) # API parameters depend on the function params = self._get_validated_parameters(function_name, request.form) # Set remaining parameters for the task (description, task_params, priority, log_level, error_log_level, keep_secs) = self._get_task_data( function_name, params ) # Queue the task db_task = task_engine.add_task( get_session_user(), description, function_name, task_params, priority, log_level, error_log_level, keep_secs ) if db_task is None: raise AlreadyExistsError("Task is already running") # Decode the params before returning db_task.params = cPickle.loads(db_task.params) tdict = object_to_dict(db_task) if tdict.get("user") is not None: # Do not give out anything password related del tdict["user"]["password"] return make_api_success_response(tdict)
def post(self): # Require folios or admin_folios permission to create a portfolio permissions_engine.ensure_permitted(SystemPermissions.PERMIT_FOLIOS, get_session_user()) db_session = data_engine.db_get_session() try: params = self._get_validated_object_parameters(request.form) folio = Folio(params['human_id'] or Folio.create_human_id(), params['name'], params['description'], get_session_user()) self._set_permissions(folio, params, db_session) data_engine.create_portfolio( folio, get_session_user(), _db_session=db_session, _commit=True # fail here if human_id not unique ) # Return a clean object the same as for get(id) folio = data_engine.get_portfolio(folio.id, load_images=True, load_history=True) folio = _prep_folio_object(folio) return make_api_success_response( object_to_dict(folio, _omit_fields)) finally: db_session.close()
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()
def delete(self, folio_id, image_id): db_session = data_engine.db_get_session() try: folio_image = data_engine.get_portfolio_image( AttrObject(id=folio_id), AttrObject(id=image_id), _db_session=db_session) if folio_image is None: raise DoesNotExistError(str(folio_id) + '/' + str(image_id)) # Check permissions permissions_engine.ensure_portfolio_permitted( folio_image.portfolio, FolioPermission.ACCESS_EDIT, get_session_user()) # Add history first so that we only commit once at the end folio = folio_image.portfolio data_engine.add_portfolio_history(folio, get_session_user(), FolioHistory.ACTION_IMAGE_CHANGE, '%s removed' % folio_image.image.src, _db_session=db_session, _commit=False) # Flag that exported zips will be out of date folio.last_updated = datetime.utcnow() # Delete the image from the portfolio and commit changes data_engine.delete_object(folio_image, _db_session=db_session, _commit=True) return make_api_success_response() finally: db_session.close()
def put(self, image_id): params = self._get_validated_object_parameters(request.form) # Get image and update it db_img = data_engine.get_image(image_id=image_id) if not db_img: raise DoesNotExistError(str(image_id)) # Require edit permission or file admin permissions_engine.ensure_folder_permitted( db_img.folder, FolderPermission.ACCESS_EDIT, get_session_user() ) old_title = db_img.title old_description = db_img.description db_img.title = params['title'] db_img.description = params['description'] data_engine.save_object(db_img) # Get text changes. Max info length = # 100 + 200 + len('()' + '()' + 'Title: ' + ' / ' + 'Description: ') ==> 327 title_diff = get_string_changes(old_title, params['title'], char_limit=100).strip() if not title_diff: # Try for deletions from title title_diff = get_string_changes(params['title'], old_title, char_limit=100).strip() if title_diff: title_diff = '(' + title_diff + ')' desc_diff = get_string_changes( old_description, params['description'], char_limit=200 ).strip() if not desc_diff: # Try for deletions from description desc_diff = get_string_changes( params['description'], old_description, char_limit=200 ).strip() if desc_diff: desc_diff = '(' + desc_diff + ')' info = '' if title_diff: info += 'Title: ' + title_diff if info and desc_diff: info += ' / ' if desc_diff: info += 'Description: ' + desc_diff # Add change history data_engine.add_image_history( db_img, get_session_user(), ImageHistory.ACTION_EDITED, info ) return make_api_success_response(object_to_dict(db_img))
def put(self, image_id): params = self._get_validated_object_parameters(request.form) # Get image and update it db_img = data_engine.get_image(image_id=image_id) if not db_img: raise DoesNotExistError(str(image_id)) # Require edit permission or file admin permissions_engine.ensure_folder_permitted( db_img.folder, FolderPermission.ACCESS_EDIT, get_session_user()) old_title = db_img.title old_description = db_img.description db_img.title = params['title'] db_img.description = params['description'] data_engine.save_object(db_img) # Get text changes. Max info length = # 100 + 200 + len('()' + '()' + 'Title: ' + ' / ' + 'Description: ') ==> 327 title_diff = get_string_changes(old_title, params['title'], char_limit=100).strip() if not title_diff: # Try for deletions from title title_diff = get_string_changes(params['title'], old_title, char_limit=100).strip() if title_diff: title_diff = '(' + title_diff + ')' desc_diff = get_string_changes(old_description, params['description'], char_limit=200).strip() if not desc_diff: # Try for deletions from description desc_diff = get_string_changes(params['description'], old_description, char_limit=200).strip() if desc_diff: desc_diff = '(' + desc_diff + ')' info = '' if title_diff: info += 'Title: ' + title_diff if info and desc_diff: info += ' / ' if desc_diff: info += 'Description: ' + desc_diff # Add change history data_engine.add_image_history(db_img, get_session_user(), ImageHistory.ACTION_EDITED, info) return make_api_success_response( object_to_dict(_prep_image_object(db_img)))
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 portfolio permissions permissions_engine.ensure_portfolio_permitted( folio, FolioPermission.ACCESS_EDIT, get_session_user()) # Get the image by either ID or src params = self._get_validated_object_parameters(request.form, True) if 'image_id' in params: image = data_engine.get_image(params['image_id'], _db_session=db_session) if image is None: raise DoesNotExistError(str(params['image_id'])) else: image = auto_sync_file(params['image_src'], data_engine, task_engine, anon_history=True, burst_pdf=False, _db_session=db_session) if image is None or image.status == Image.STATUS_DELETED: raise DoesNotExistError(params['image_src']) # Check image permissions permissions_engine.ensure_folder_permitted( image.folder, FolderPermission.ACCESS_VIEW, get_session_user(), False) # Add history first so that we only commit once at the end data_engine.add_portfolio_history(folio, get_session_user(), FolioHistory.ACTION_IMAGE_CHANGE, '%s added' % image.src, _db_session=db_session, _commit=False) # Flag that exported zips are now out of date folio.last_updated = datetime.utcnow() # Add the image and commit changes db_folio_image = data_engine.save_object(FolioImage( folio, image, params['image_parameters'], params['filename'], params['index']), refresh=True, _db_session=db_session, _commit=True) return make_api_success_response( object_to_dict(_prep_folioimage_object(db_folio_image), _omit_fields + ['portfolio'])) finally: db_session.close()
def put(self, folio_id): db_session = data_engine.db_get_session() try: # Get 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()) # Update the object params = self._get_validated_object_parameters(request.form) permissions_changed = self._set_permissions( folio, params, db_session) changes = [] if params['human_id'] != folio.human_id: changes.append('short URL changed') if params['name'] != folio.name: changes.append('name changed') if params['description'] != folio.description: changes.append('description changed') if permissions_changed: changes.append('permissions changed') folio.human_id = params['human_id'] or Folio.create_human_id() folio.name = params['name'] folio.description = params['description'] # Note: folio.last_updated is only for image changes # (to know when to invalidate the exported zips) data_engine.add_portfolio_history(folio, get_session_user(), FolioHistory.ACTION_EDITED, ', '.join(changes).capitalize(), _db_session=db_session, _commit=False) data_engine.save_object( folio, _db_session=db_session, _commit=True # fail here if human_id not unique ) if permissions_changed: permissions_engine.reset_portfolio_permissions() # Return a clean object the same as for get(id) folio = data_engine.get_portfolio(folio.id, load_images=True, load_history=True) folio = _prep_folio_object(folio) return make_api_success_response( object_to_dict(folio, _omit_fields)) finally: db_session.close()
def put(self, folio_id, image_id): db_session = data_engine.db_get_session() try: folio_image = data_engine.get_portfolio_image( AttrObject(id=folio_id), AttrObject(id=image_id), _db_session=db_session) if folio_image is None: raise DoesNotExistError(str(folio_id) + '/' + str(image_id)) # Check permissions permissions_engine.ensure_portfolio_permitted( folio_image.portfolio, FolioPermission.ACCESS_EDIT, get_session_user()) # Update the object with any/all parameters that were passed in params = self._get_validated_object_parameters(request.form, False) changes = [] affects_zips = False if (params['image_parameters'] is not None and params['image_parameters'] != folio_image.parameters): folio_image.parameters = params['image_parameters'] changes.append('image attributes changed') affects_zips = True if (params['filename'] is not None and params['filename'] != folio_image.filename): folio_image.filename = params['filename'] changes.append('filename changed') affects_zips = True if (params['index'] is not None and params['index'] != folio_image.order_num): folio_image.order_num = params['index'] changes.append('set as position %d' % (params['index'] + 1)) if changes: # Flag if exported zips will be out of date if affects_zips: folio_image.portfolio.last_updated = datetime.utcnow() # Add history and commit changes data_engine.add_portfolio_history( folio_image.portfolio, get_session_user(), FolioHistory.ACTION_IMAGE_CHANGE, '%s updated: %s' % (folio_image.image.src, ', '.join(changes)), _db_session=db_session, _commit=True) return make_api_success_response( object_to_dict(_prep_folioimage_object(folio_image), _omit_fields + ['portfolio'])) finally: db_session.close()
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))
def get(self, folder_id=None): """ Gets a folder by path or ID, returning 1 level of children (sub-folders) """ if folder_id is None: # Get folder from path, using auto_sync to pick up new and deleted disk folders path = self._get_validated_path_arg(request) db_folder = auto_sync_folder(path, data_engine, task_engine) if db_folder is None: raise DoesNotExistError(path) else: # Get folder from ID db_folder = data_engine.get_folder(folder_id) if db_folder is None: raise DoesNotExistError(str(folder_id)) # View permission is required (ignoring view permission on parent+children) permissions_engine.ensure_folder_permitted( db_folder, FolderPermission.ACCESS_VIEW, get_session_user()) # Get the folder again, this time with parent and children # (children possibly faked - see the get_folder() docs - which is why # we can't use db_folder mk2 normally, only serialize it and exit) status_filter = self._get_validated_status_arg(request) db_folder = data_engine.get_folder(db_folder.id, load_parent=True, load_children=True, children_status=status_filter) if db_folder is None: raise DoesNotExistError(str(folder_id)) return make_api_success_response(object_to_dict(db_folder))
def post(self, group_id): params = self._get_validated_object_parameters(request.form) group = data_engine.get_group(group_id=group_id, load_users=True) if group is None: raise DoesNotExistError(str(group_id)) # Check permissions! The current user must have user admin to be here. # But if they don't also have permissions admin or superuser then we # must block the change if the new group would grant one of the same. if group.permissions.admin_permissions or group.permissions.admin_all: if not permissions_engine.is_permitted( SystemPermissions.PERMIT_ADMIN_PERMISSIONS, get_session_user() ): raise SecurityError( 'You cannot add users to a group that ' + 'grants permissions administration, because you do not ' + 'have permissions administration access yourself.' ) user = data_engine.get_user(user_id=params['user_id']) if user is not None: if user not in group.users: group.users.append(user) data_engine.save_object(group) permissions_engine.reset() return make_api_success_response()
def portfolio_download(human_id, filename): logger.debug('GET ' + request.url) try: # Find the portfolio folio = data_engine.get_portfolio(human_id=human_id) if not folio: raise DoesNotExistError('Portfolio \'%s\' does not exist' % human_id) # Ensure that the user has permission to download the portfolio user = get_session_user() permissions_engine.ensure_portfolio_permitted( folio, FolioPermission.ACCESS_DOWNLOAD, user) # Check that the filename is valid (note: assumes folio.downloads is eager loaded) if not filename: raise DoesNotExistError('No filename specified') folio_exports = [ dl for dl in folio.downloads if dl.filename == filename ] if not folio_exports: raise DoesNotExistError('Download \'%s\' is not available' % filename) folio_export = folio_exports[0] # The physical file should always exist when the data+filename exists # This also checks that the file path lies inside IMAGES_BASE_DIR zip_path = get_portfolio_export_file_path(folio_export) ensure_path_exists(zip_path, require_file=True) # Prepare to serve the file response = send_file( get_abs_path(zip_path), mimetype='application/zip', as_attachment=True, conditional=True, cache_timeout=31536000 # zips never change once created ) # Lastly write an audit record data_engine.add_portfolio_history(folio, user, FolioHistory.ACTION_DOWNLOADED, folio_export.filename) return response except httpexc.HTTPException: # Pass through HTTP 4xx and 5xx raise except SecurityError as e: if app.config['DEBUG']: raise log_security_error(e, request) raise httpexc.Forbidden() except DoesNotExistError as e: logger.warning('404 Not found: ' + str(e)) raise httpexc.NotFound(safe_error_str(e)) except Exception as e: if app.config['DEBUG']: raise logger.error('500 Error for ' + request.url + '\n' + str(e)) raise httpexc.InternalServerError(safe_error_str(e))
def post(self, group_id): params = self._get_validated_object_parameters(request.form) group = data_engine.get_group(group_id=group_id, load_users=True) if group is None: raise DoesNotExistError(str(group_id)) # Check permissions! The current user must have user admin to be here. # But if they don't also have permissions admin or superuser then we # must block the change if the new group would grant one of the same. if group.permissions.admin_permissions or group.permissions.admin_all: if not permissions_engine.is_permitted( SystemPermissions.PERMIT_ADMIN_PERMISSIONS, get_session_user()): raise SecurityError( 'You cannot add users to a group that ' + 'grants permissions administration, because you do not ' + 'have permissions administration access yourself.') user = data_engine.get_user(user_id=params['user_id']) if user is not None: if user not in group.users: group.users.append(user) data_engine.save_object(group) reset_user_sessions(user) permissions_engine.reset() return make_api_success_response()
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
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 ))
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()
def post(self): permissions_engine.ensure_permitted( SystemPermissions.PERMIT_SUPER_USER, get_session_user()) params = self._get_validated_object_parameters(request.form) template = ImageTemplate(params['name'], params['description'], params['template']) template = data_engine.save_object(template, refresh=True) image_engine.reset_templates() return self.get(template.id)
def get(self, image_id): db_img = data_engine.get_image(image_id=image_id) if not db_img: raise DoesNotExistError(str(image_id)) else: # Require view permission or file admin permissions_engine.ensure_folder_permitted( db_img.folder, FolderPermission.ACCESS_VIEW, get_session_user()) return make_api_success_response( object_to_dict(_prep_image_object(db_img)))
def token_login(): status = API_CODES.SUCCESS err_msg = '' token = request.args.get('token', '') next_url = request.args.get('next', '') try: if token: token_auth_class = app.config['API_AUTHENTICATION_CLASS'] auth_cls = getattr(flask_ext, token_auth_class, None) if not auth_cls: raise ValueError('Class flask_ext.%s was not found' % token_auth_class) auth_module = auth_cls(app) auth_object = auth_module.decode_auth_token(token) if auth_object: # The token is valid - set as logged in on the API auth_module.set_authenticated(auth_object) # Now set as logged in on the web session too auth_user = session_manager.get_session_user() if not auth_user: raise ValueError( 'Internal error - no session user returned - has BaseHttpAuthentication ' 'or session_manager been changed?' ) session_manager.log_in(auth_user) else: status = API_CODES.UNAUTHORISED err_msg = 'Invalid or expired token' else: status = API_CODES.INVALID_PARAM err_msg = 'No token value supplied' except SecurityError as se: if app.config['DEBUG']: raise log_security_error(se, request) status = API_CODES.UNAUTHORISED err_msg = str(se) except Exception as e: if app.config['DEBUG']: raise logger.error('Error performing API token to web login: '******'Sorry, an error occurred. Please try again later.' finally: if status != API_CODES.SUCCESS: session_manager.log_out() if next_url and status == API_CODES.SUCCESS: return redirect(next_url) else: return render_template( 'token_login.html', err_msg=safe_error_str(err_msg) ), status
def get(self, image_id): db_img = data_engine.get_image(image_id=image_id) if not db_img: raise DoesNotExistError(str(image_id)) else: # Require view permission or file admin permissions_engine.ensure_folder_permitted( db_img.folder, FolderPermission.ACCESS_VIEW, get_session_user() ) return make_api_success_response(object_to_dict(db_img))
def put(self, template_id): permissions_engine.ensure_permitted( SystemPermissions.PERMIT_SUPER_USER, get_session_user()) params = self._get_validated_object_parameters(request.form) template = data_engine.get_image_template(template_id) if template is None: raise DoesNotExistError(str(template_id)) template.name = params['name'] template.description = params['description'] template.template = params['template'] data_engine.save_object(template) image_engine.reset_templates() return self.get(template.id)
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))
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
def put(self, folder_id): """ Moves or renames a disk folder """ params = self._get_validated_parameters(request.form) # Run this as a background task in case it takes a long time task = task_engine.add_task(get_session_user(), 'Move disk folder %d' % folder_id, 'move_folder', { 'folder_id': folder_id, 'path': params['path'] }, Task.PRIORITY_HIGH, 'info', 'error', 10) if task is None: # Task already submitted return make_api_success_response(task_accepted=True) else: return self._task_response(task, 30)
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()
def put(self, folio_id, image_id): db_session = data_engine.db_get_session() try: # Get data folio_image = data_engine.get_portfolio_image( AttrObject(id=folio_id), AttrObject(id=image_id), _db_session=db_session) if folio_image is None: raise DoesNotExistError(str(folio_id) + '/' + str(image_id)) # Check permissions permissions_engine.ensure_portfolio_permitted( folio_image.portfolio, FolioPermission.ACCESS_EDIT, get_session_user()) # Update the portfolio params = self._get_validated_object_parameters(request.form) chd_folio_image = data_engine.reorder_portfolio( folio_image, params['index']) data_engine.add_portfolio_history( folio_image.portfolio, get_session_user(), FolioHistory.ACTION_IMAGE_CHANGE, '%s moved to position %d' % (folio_image.image.src, chd_folio_image.order_num + 1), _db_session=db_session, _commit=True) # Return the updated image list db_session.expire(folio_image) folio = data_engine.get_portfolio(folio_id, load_images=True, _db_session=db_session) image_list = [_prep_folioimage_object(fi) for fi in folio.images] return make_api_success_response( object_to_dict_list(image_list, _omit_fields + ['portfolio'])) finally: db_session.close()
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) 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 cache permissions_engine.reset() return make_api_success_response()
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()
def get(self, folio_id=None): # v4.1 Added support for /api/portfolios/?human_id=<human id> human_id = request.args.get('human_id', '') if not folio_id and not human_id: # List portfolios that the user can view folio_list = data_engine.list_portfolios( get_session_user(), FolioPermission.ACCESS_VIEW) folio_list = [_prep_folio_object(f) for f in folio_list] return make_api_success_response( object_to_dict_list(folio_list, _omit_fields)) else: # Get single portfolio by either ID or v4.1 by human ID folio = data_engine.get_portfolio(folio_id, human_id, load_images=True, load_history=True) if folio is None: raise DoesNotExistError(human_id or str(folio_id)) # Check permissions permissions_engine.ensure_portfolio_permitted( folio, FolioPermission.ACCESS_VIEW, get_session_user()) folio = _prep_folio_object(folio) return make_api_success_response( object_to_dict(folio, _omit_fields))
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))
def delete(self, folio_id): # Get 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_DELETE, get_session_user()) # Double check the downloads were eager-loaded before we try to use them below if not data_engine.attr_is_loaded(folio, 'downloads'): raise ValueError('bug: folio.downloads should be present') # Delete - cascades to folio images, permissions, history, and exports data_engine.delete_object(folio) # If we got this far the database delete worked and we now need to # delete the exported zip files (if any) delete_dir(get_portfolio_directory(folio), recursive=True) return make_api_success_response()
def _set_permissions(self, group, params): # Apply default permissions if this is a new group if not group.permissions: group.permissions = SystemPermissions( group, False, False, False, False, False, False, False ) # Update permissions only if the current user has permissions admin if permissions_engine.is_permitted( SystemPermissions.PERMIT_ADMIN_PERMISSIONS, get_session_user() ): group.permissions.folios = params['access_folios'] group.permissions.reports = params['access_reports'] group.permissions.admin_users = params['access_admin_users'] group.permissions.admin_files = params['access_admin_files'] group.permissions.admin_folios = params['access_admin_folios'] group.permissions.admin_permissions = params['access_admin_permissions'] group.permissions.admin_all = params['access_admin_all'] return True return False
def _set_permissions(self, group, params): # Apply default permissions if this is a new group if not group.permissions: group.permissions = SystemPermissions(group, False, False, False, False, False, False, False) # Update permissions only if the current user has permissions admin if permissions_engine.is_permitted( SystemPermissions.PERMIT_ADMIN_PERMISSIONS, get_session_user()): group.permissions.folios = params['access_folios'] group.permissions.reports = params['access_reports'] group.permissions.admin_users = params['access_admin_users'] group.permissions.admin_files = params['access_admin_files'] group.permissions.admin_folios = params['access_admin_folios'] group.permissions.admin_permissions = params[ 'access_admin_permissions'] group.permissions.admin_all = params['access_admin_all'] return True return False
def delete(self, folder_id): """ Deletes a disk folder """ # v4.1 #10 delete_folder() doesn't care whether it exists, but we want the # API to return a "not found" if the folder doesn't exist on disk # (and as long as the database is already in sync with that) db_folder = data_engine.get_folder(folder_id) if db_folder is None: raise DoesNotExistError(str(folder_id)) if not path_exists(db_folder.path, require_directory=True ) and db_folder.status == Folder.STATUS_DELETED: raise DoesNotExistError(db_folder.path) # Run this as a background task in case it takes a long time task = task_engine.add_task(get_session_user(), 'Delete disk folder %d' % folder_id, 'delete_folder', {'folder_id': folder_id}, Task.PRIORITY_HIGH, 'info', 'error', 10) if task is None: # Task already submitted return make_api_success_response(task_accepted=True) else: return self._task_response(task, 30)
def imagedetails(): # Get/check parameters try: src = request.args.get('src', '') validate_string(src, 1, 1024) except ValueError as e: raise ParameterError(e) # 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(_image_dict(db_image))
def delete(self, image_id): """ Deletes a file from disk """ # Get image data db_img = data_engine.get_image(image_id=image_id) if not db_img: raise DoesNotExistError(str(image_id)) # v4.1 #10 delete_file() doesn't care whether the file exists, but we # want the API to return a "not found" if the file doesn't exist # (and as long as the database is already in sync with that) if not path_exists( db_img.src, require_file=True) and db_img.status == Image.STATUS_DELETED: raise DoesNotExistError(db_img.src) # Delete db_img = delete_file(db_img, get_session_user(), data_engine, permissions_engine) # Remove cached images for 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)))
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 }
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 get(self, task_id): db_task = task_engine.get_task(task_id=task_id, decode_attrs=True) if not db_task: raise DoesNotExistError(str(task_id)) else: # Requires super user or task owner if not db_task.user or db_task.user.id != get_session_user_id(): permissions_engine.ensure_permitted(SystemPermissions.PERMIT_SUPER_USER, get_session_user()) tdict = object_to_dict(db_task) if tdict.get("user") is not None: # Do not give out anything password related del tdict["user"]["password"] return make_api_success_response(tdict)
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)