def fsDoMoveFileView(quotedFilePath, quotedDestBox=''): """Move-file, view 2/2: dest box is selected, actually move.""" user = g.user db = dbGetDatabase() # we retrieve the source file and the destination box srcFsPathString = urllib.parse.unquote_plus(quotedFilePath) srcLsPath = splitPathString(srcFsPathString) srcBoxPath, fileName = srcLsPath[:-1], srcLsPath[-1] srcBox = getBoxFromPath(db, srcBoxPath, user) file = getFileFromParent(db, srcBox, fileName, user) dstBoxPathString = urllib.parse.unquote_plus(quotedDestBox) dstBoxPath = splitPathString(dstBoxPathString) dstBox = getBoxFromPath(db, dstBoxPath, user) fileStorageDirectory = g.settings['system']['system_directories'][ 'fs_directory']['value'] # request._onErrorUrl = url_for( 'lsView', lsPathString='/'.join(srcBoxPath[1:]), ) messages = moveFile( db, file, srcBox, dstBox, user, fileStorageDirectory=fileStorageDirectory, ) for msg in messages: flashMessage('Info', 'Info', msg) return redirect(url_for( 'lsView', lsPathString='/'.join(dstBoxPath[1:]), ))
def logoutView(): """Logout view.""" if g.user is not None and g.user.is_authenticated: flashMessage( 'Success', 'Logged out', 'farewell, %s.' % g.user.fullname, ) logout_user() return redirect(url_for('indexView'))
def exceptionHandler(error): """ Generic catcher for in-route errors. Handles all exceptions, in particular through the use of the 'exceptions' module they end up as flashed information. Preferentially redirects to request._onErrorUrl if it is set, otherwise falls back to "index". NOTE: in development this is simply re-raising the error for the debugger. """ if app.config.get('DEVELOPMENT', False): raise error else: flashableError = exceptionToFlashable(error) flashMessage(**flashableError) if hasattr(request, '_onErrorUrl'): return redirect(request._onErrorUrl) else: return redirect(url_for('lsView'))
def fsMakeTicketView(fsPathString=''): """Create-file-ticket (file-read) view.""" user = g.user lsPath = splitPathString(fsPathString) boxPath, fileName = lsPath[:-1], lsPath[-1] db = dbGetDatabase() parentBox = getBoxFromPath(db, boxPath, user) request._onErrorUrl = url_for( 'lsView', lsPathString='/'.join(boxPath[1:]), ) if parentBox is not None: file = getFileFromParent(db, parentBox, fileName, user) if file is not None: pathBCrumbs = makeBreadCrumbs( boxPath, g, appendedItems=[ { 'kind': 'file', 'target': file, }, { 'name': '(create ticket)', 'kind': 'link', 'target': url_for( 'fsMakeTicketView', fsPathString=fsPathString, ), 'link': False, }, ], ) # if this succeeded, user has read permission on 'file' form = generateFsTicketForm( fileModePresent=True, settings=g.settings, ) if form.validate_on_submit(): magicLink = dbMakeFileTicket( db=db, ticketName=form.name.data, validityHours=transformIfNotEmpty( form.validityhours.data, int, ), multiplicity=transformIfNotEmpty( form.multiplicity.data, int, ), ticketMessage=transformIfNotEmpty( form.ticketmessage.data, ), file=file, fileMode=form.filemode.data, lsPath=lsPath, user=user, urlRoot=request.url_root, settings=g.settings, ) flashMessage( 'Success', 'Done', ('File ticket generated. Give the recipient ' 'the following magic link:'), pillText=magicLink, ) return redirect( url_for( 'lsView', lsPathString='/'.join(boxPath[1:]), )) else: form.name.data = applyDefault(form.name.data, file.name) form.ticketmessage.data = applyDefault( form.ticketmessage.data, 'Please access this file', ) form.filemode.data = applyDefault( form.filemode.data, 'direct', additionalNulls=['None'], ) maxValidityHours = g.settings['behaviour'][ 'behaviour_tickets']['max_ticket_validityhours']['value'] form.validityhours.data = applyDefault( form.validityhours.data, (str(maxValidityHours) if maxValidityHours is not None else ''), ) maxMultiplicity = g.settings['behaviour']['behaviour_tickets'][ 'max_ticket_multiplicity']['value'] form.multiplicity.data = applyDefault( form.multiplicity.data, (str(maxMultiplicity) if maxMultiplicity is not None else ''), ) return render_template( 'fsticket.html', pageTitle='Create public file ticket', pageSubtitle=('Recipient(s) of the ticket will be able ' 'to access "%s" without an account.') % (fsPathString, ), baseMultiplicityCaption=('Number of granted accesses (bo' 'th views and downloads count)'), user=user, form=form, iconUrl=makeSettingImageUrl( g, 'app_images', 'file_ticket', ), showFileMode=True, breadCrumbs=pathBCrumbs, ) else: return abort(404) else: return abort(404)
def downloadBoxView(boxPathString=''): user = g.user lsPath = splitPathString(boxPathString) db = dbGetDatabase() # canDownloadArchive = userCanDownloadArchive( db, user, g.settings, ) # if not canDownloadArchive: return abort(404) else: thisBox = getBoxFromPath(db, lsPath, user) if thisBox is not None: boxPath = lsPath[1:] tempFileDirectory = g.settings['system']['system_directories'][ 'temp_directory']['value'] fileStorageDirectory = g.settings['system']['system_directories'][ 'fs_directory']['value'] # we extract the tree "from this box downward" tree = collectTreeFromBox(db, thisBox, user, admitFiles=True) overallSize = treeSize(tree) # size-dependent handling by reading settings mibSize = bytesToMiB(overallSize) archiveWarningSizeMib = g.settings['behaviour']['archives'][ 'archive_download_alert_size']['value'] archiveBlockSizeMib = g.settings['behaviour']['archives'][ 'archive_download_blocking_size']['value'] if not optionNumberLeq(archiveBlockSizeMib, mibSize): # archive is simply too big (hard limit) oeText = ('Cannot prepare archive: box size, %s' ', exceeds configured limits') raise OstracionError(oeText % formatBytesSize(overallSize)) else: # we may have to issue a warning to the downloader: # this if (1) rather large archive, # (2) no previous 'ok' was given hasConfirmed = request.args.get('confirmed') == 'yes' if all([ not optionNumberLeq(archiveWarningSizeMib, mibSize), not hasConfirmed, ]): # we issue the warning and wait confirmText = ('Preparing this archive may take some ' 'time due to the size of the box (%s). ' 'Click this message to really proceed ' 'with the download.') flashMessage( 'Warning', 'Caution', confirmText % formatBytesSize(overallSize), url=url_for( 'downloadBoxView', boxPathString=boxPathString, confirmed='yes', ), ) return redirect( url_for( 'lsView', lsPathString=boxPathString, )) else: # we collect the information needed to prepare the # archive file. For now, no empty boxes # (a zip format limitation) def collectArchivablePairs(tr, accPath=''): hereFiles = [{ 'type': 'file', 'path': fileIdToPath( file['file'].file_id, fileStorageDirectory, ), 'zip_path': os.path.join( accPath, file['file'].name, ), } for file in tr['contents']['files']] + [{ 'type': 'data', 'data': link['link'].formatAsString(), 'zip_path': os.path.join( accPath, '%s' % link['link'].name, ), } for link in tr['contents']['links']] subFiles = [ fp for subBox in tr['contents']['boxes'] for fp in collectArchivablePairs(subBox, accPath=os.path.join( accPath, subBox['box'].box_name, )) ] return hereFiles + subFiles # we make the tree into a list of filePairs, ready to zip inPairs = collectArchivablePairs(tree) filePairs = [p for p in inPairs if p['type'] == 'file'] dataPairs = [p for p in inPairs if p['type'] == 'data'] # we create the zip _, archiveFileTitle = temporarySplitFileName( tempFileDirectory, ) makeZipFile( os.path.join(tempFileDirectory, archiveFileTitle), filePairs, dataPairs, ) # Now the file exists, ready to be served. # Instead of a plain send_from_directory, though, # we use the answer "Stream file, then delete" # by Sean Vieira here: # https://stackoverflow.com/questions/24612366/ # delete-an-uploaded-file-after-downloading-it-from-flask def StreamFileAndRemoveIt(localFileName): fileHandle = open(localFileName, 'rb') yield from fileHandle fileHandle.close() os.remove(localFileName) zipFileName = '%s.zip' % describeBoxName(thisBox, ) contentDisposition = 'attachment; filename="%s"' % ( zipFileName, ) return current_app.response_class( StreamFileAndRemoveIt(localFileName=os.path.join( tempFileDirectory, archiveFileTitle, ), ), headers={ 'Content-Disposition': contentDisposition, }, mimetype='application/zip', ) else: request._onErrorUrl = url_for( 'lsView', lsPathString='/'.join(lsPath[1:-1]), ) raise OstracionWarning('Cannot access the specified box "%s"' % lsPath[-1])
def createUserUponTicket(db, user, issuer, richTicket, urlRoot): """User-creation-ticket route.""" ticketsByformerAdminsAreProtected = g.settings['behaviour'][ 'behaviour_tickets']['protect_nonadmin_user_password_tickets']['value'] appLongName = g.settings['behaviour']['behaviour_appearance'][ 'application_long_name']['value'] if not ticketsByformerAdminsAreProtected or userIsAdmin(db, issuer): form = generateNewUserForm(g.settings) if form.validate_on_submit(): userDict = { 'username': form.username.data, 'fullname': form.fullname.data, 'email': form.email.data, 'password': form.password.data, } if ('username' in richTicket['metadata'] and richTicket['metadata']['username'] != userDict['username']): raise RuntimeError( 'Detected attempt to escape username set by ticket issuer') else: # the usename must not exist (relevant if left free in ticket) if isUsername(db, userDict['username'], user): if 'username' in richTicket['metadata']: request._onErrorUrl = url_for('lsView') else: request._onErrorUrl = url_for( 'redeemTicketView', mode='u', ticketId=ticketId, securityCode=securityCode, ) raise OstracionError('Username "%s" exists already' % userDict['username']) else: # we proceed with the user creation proper newUser = User( icon_file_id='', icon_file_id_username=userDict['username'], banned=0, terms_accepted='0', terms_accepted_version='', **userDict, ) try: dbPunchRichTicket(db, richTicket, skipCommit=True) dbCreateUser(db, newUser, user) # flashMessage( 'Info', 'Success', 'User created. You can now log in to %s' % (appLongName), ) return redirect(url_for('loginView')) except Exception as e: db.rollback() raise e else: form.username.data = applyDefault( form.username.data, richTicket['metadata'].get('username', ''), ) form.fullname.data = applyDefault( form.fullname.data, richTicket['metadata'].get('fullname', ''), ) form.email.data = applyDefault( form.email.data, richTicket['metadata'].get('email', ''), ) return render_template( 'newuser.html', form=form, user=user, pageTitle='Create user', pageSubtitle='Create your user account on %s%s' % ( appLongName, ('' if richTicket['metadata'].get('message') is None else '. Message on ticket: "%s"' % (richTicket['metadata']['message'])), ), iconUrl=makeSettingImageUrl(g, 'user_images', 'user_icon'), lockUsernameField='username' in richTicket['metadata'], ) else: raise OstracionError('Ticket not acessible or expired') return redirect(url_for('lsView'))
def changePasswordUponTicket(db, user, issuer, richTicket, urlRoot): """Change-password-ticket route.""" ticketsByformerAdminsAreProtected = g.settings['behaviour'][ 'behaviour_tickets']['protect_nonadmin_user_password_tickets']['value'] applicationLongName = g.settings['behaviour']['behaviour_appearance'][ 'application_long_name']['value'] if not ticketsByformerAdminsAreProtected or userIsAdmin(db, issuer): form = generateTicketChangePasswordForm(g.settings) if form.validate_on_submit(): changeeUser = dbGetUser(db, richTicket['metadata']['username']) if form.username.data == changeeUser.username: # newPassword = form.newpassword.data minPasswordLength = g.settings['behaviour'][ 'behaviour_security']['password_min_length']['value'] if len(form.newpassword.data) < minPasswordLength: print('richTicket ', richTicket) flashMessage( 'Info', 'Please retry', 'New password too short (min. %i characters)' % (minPasswordLength, ), ) return redirect( url_for( 'redeemTicketView', mode='p', ticketId=richTicket['ticket'].ticket_id, securityCode=richTicket['ticket'].security_code, )) else: newUser = User( **({(k if k != 'passwordhash' else 'password'): ( v if k != 'passwordhash' else form.newpassword.data ) for k, v in changeeUser.asDict().items()})) try: dbPunchRichTicket(db, richTicket, skipCommit=True) dbUpdateUser(db, newUser, user, skipCommit=True) db.commit() # flash a message flashMessage( 'Success', 'Success', 'Password changed. You can now log in to %s' % (applicationLongName, ), ) # go to login return redirect(url_for('loginView')) except Exception as e: db.rollback() raise e else: raise OstracionError('Wrong username provided') else: pageSubtitle = ('Complete the process by entering your ' 'new password twice%s') % ( '' if richTicket['metadata'].get('message') is None else '. Message on ticket: "%s"' % (richTicket['metadata'].get('message'), )) return render_template( 'userchangepassword.html', user=user, form=form, showOldPasswordField=False, mode='ticket', breadCrumbs=None, pageTitle='Change password', pageSubtitle=pageSubtitle, iconUrl=makeSettingImageUrl(g, 'admin_images', 'change_password_ticket'), backToUrl=url_for('lsView'), ) else: raise OstracionError('Ticket not acessible or expired')
def uploadFilesUponTicket(db, user, issuer, richTicket, urlRoot): """ Upload-file(s)-upon-ticket route. Upload fails if 1. user banned; 2. issuer has no upload permission (incl. access) on the target box. """ noBandUsrTickets = g.settings['behaviour']['behaviour_tickets'][ 'protect_banned_user_tickets']['value'] if not noBandUsrTickets or issuer.banned == 0: boxPath = richTicket['metadata']['box_path'] request._onErrorUrl = url_for( 'lsView', lsPathString='/'.join(boxPath[1:]), ) box = getBoxFromPath(db, boxPath[1:], issuer) fileStorageDirectory = g.settings['system']['system_directories'][ 'fs_directory']['value'] if userHasPermission(db, issuer, box.permissions, 'w'): # ticket is completely valid: here the form # handling (get/post) occurs uploadsLeft = (None if richTicket['ticket'].multiplicity is None else (richTicket['ticket'].multiplicity - richTicket['ticket'].times_redeemed)) form = UploadMultipleFilesForm() if form.validate_on_submit(): uploadedFiles = [ uf for uf in request.files.getlist('files') if uf.filename != '' ] filesdescription = form.filesdescription.data # are there too many files? if uploadsLeft is None or len(uploadedFiles) <= uploadsLeft: filesToUpload = [{ 'box_id': box.box_id, 'name': secure_filename(uploadedFile.filename), 'description': form.filesdescription.data, 'date': datetime.datetime.now(), 'fileObject': uploadedFile, } for uploadedFile in uploadedFiles] # we punch the ticket as many times as # there were files provided dbPunchRichTicket( db, richTicket, numPunches=len(filesToUpload), ) makeThumbnails = g.settings['behaviour'][ 'behaviour_appearance']['extract_thumbnails']['value'] savingResult = saveAndAnalyseFilesInBox( db=db, files=filesToUpload, parentBox=box, user=issuer, fileStorageDirectory=fileStorageDirectory, thumbnailFormat=('thumbnail' if makeThumbnails else None), ) flashMessage('Success', 'Info', savingResult) return redirect(url_for( 'lsView', lsPathString='', )) else: request._onErrorUrl = url_for( 'redeemTicketView', mode='c', ticketId=richTicket['ticket'].ticket_id, securityCode=richTicket['ticket'].security_code, ) raise OstracionError( ('Cannot upload this many files ' '(maximum %i allowed)') % uploadsLeft) else: return render_template( 'uploadmultiplefiles.html', form=form, user=user, breadCrumbs=[], pageTitle='Upload file(s)', pageSubtitle='Upload%s files%s' % ( ('' if uploadsLeft is None else ' up to %i' % uploadsLeft), ('' if richTicket['metadata'].get('message') is None else ('. Message on ticket: "%s"' % (richTicket['metadata']['message']))), ), iconUrl=makeSettingImageUrl( g, 'app_images', ('single_upload' if optionNumberLeq(1, uploadsLeft) else 'multiple_upload'), ), ) else: # issuer has no write permission in box raise OstracionError('Ticket not acessible or expired') else: raise OstracionError('Ticket not acessible or expired')
def makeTicketBoxUploadView(boxPathString=''): """Make-upload-ticket (to a box) route.""" user = g.user db = dbGetDatabase() boxPath = splitPathString(boxPathString) box = getBoxFromPath(db, boxPath, user) request._onErrorUrl = url_for( 'lsView', lsPathString='/'.join(boxPath[1:]), ) # if userHasPermission(db, user, box.permissions, 'w'): # pathBCrumbs = makeBreadCrumbs( boxPath, g, appendedItems=[ { 'name': '(create upload ticket)', 'kind': 'link', 'target': url_for( 'makeTicketBoxUploadView', boxPathString=boxPathString, ), 'link': False, }, ], ) form = generateFsTicketForm( fileModePresent=False, settings=g.settings, ) if form.validate_on_submit(): magicLink = dbMakeUploadTicket( db=db, ticketName=form.name.data, validityHours=transformIfNotEmpty( form.validityhours.data, int, ), multiplicity=transformIfNotEmpty( form.multiplicity.data, int, ), ticketMessage=transformIfNotEmpty( form.ticketmessage.data, ), box=box, boxPath=boxPath, user=user, urlRoot=request.url_root, settings=g.settings, ) flashMessage( 'Success', 'Done', ('Upload ticket generated. Give the recipient ' 'the following magic link:'), pillText=magicLink, ) return redirect(url_for( 'lsView', lsPathString='/'.join(boxPath[1:]), )) else: defaultName = 'Upload-To-%s' % ( describeBoxName(box) ) form.name.data = applyDefault(form.name.data, defaultName) form.ticketmessage.data = applyDefault( form.ticketmessage.data, 'Please, upload files to this box', ) maxValidityHours = g.settings['behaviour']['behaviour_tickets'][ 'max_ticket_validityhours']['value'] form.validityhours.data = applyDefault( form.validityhours.data, str(maxValidityHours) if maxValidityHours is not None else '', ) maxMultiplicity = g.settings['behaviour']['behaviour_tickets'][ 'max_ticket_multiplicity']['value'] form.multiplicity.data = applyDefault( form.multiplicity.data, str(maxMultiplicity) if maxMultiplicity is not None else '', ) return render_template( 'fsticket.html', pageTitle='Create public upload ticket', pageSubtitle=('Recipient(s) of the ticket will be able to ' 'upload to "%s", without an account, on ' 'your behalf.') % ( describeBoxTitle(box) ), baseMultiplicityCaption='Number of granted file uploads', user=user, form=form, iconUrl=makeSettingImageUrl(g, 'app_images', 'upload_ticket'), showFileMode=False, breadCrumbs=pathBCrumbs, ) # else: raise OstracionError('Insufficient permissions')
def makeTextFileView(fsPathString=''): """Generate-new-text-file route.""" user = g.user form = MakeTextFileForm() db = dbGetDatabase() boxPath = splitPathString(fsPathString) request._onErrorUrl = url_for( 'lsView', lsPathString='/'.join(boxPath[1:]), ) fileStorageDirectory = g.settings['system']['system_directories'][ 'fs_directory']['value'] parentBoxPath = boxPath parentBox = getBoxFromPath(db, parentBoxPath, user) if form.validate_on_submit(): fileName = secure_filename(form.filename.data) savableFile = SavableTextFile('') filesToUpload = [ { 'box_id': parentBox.box_id, 'name': fileName, 'description': form.filedescription.data, 'date': datetime.datetime.now(), 'fileObject': savableFile, } ] makeThumbnails = g.settings['behaviour']['behaviour_appearance'][ 'extract_thumbnails']['value'] savingResult = saveAndAnalyseFilesInBox( db=db, files=filesToUpload, parentBox=parentBox, user=user, fileStorageDirectory=fileStorageDirectory, thumbnailFormat='thumbnail' if makeThumbnails else None, pastActionVerbForm='created', ) flashMessage('Success', 'Info', savingResult) return redirect(url_for( 'editTextFileView', fsPathString='/'.join(parentBoxPath[1:] + [fileName]), )) else: pathBCrumbs = makeBreadCrumbs( parentBoxPath, g, appendedItems=[{ 'kind': 'link', 'target': None, 'name': 'New text', }], ) return render_template( 'maketextfile.html', form=form, user=user, breadCrumbs=pathBCrumbs, iconUrl=pickFileThumbnail('text/plain'), pageTitle='New text', pageSubtitle='Create a new text file in "%s"' % ( describeBoxTitle(parentBox) ), )
def makeTicketBoxGalleryView(boxPathString=''): """Make-gallery-ticket-of-box route.""" user = g.user db = dbGetDatabase() boxPath = splitPathString(boxPathString) box = getBoxFromPath(db, boxPath, user) request._onErrorUrl = url_for('lsView', lsPathString='/'.join(boxPath[1:])) # pathBCrumbs = makeBreadCrumbs( boxPath, g, appendedItems=[ { 'name': '(create gallery ticket)', 'kind': 'link', 'target': url_for( 'makeTicketBoxGalleryView', boxPathString=boxPathString, ), 'link': False, }, ], ) form = generateFsTicketForm( fileModePresent=False, settings=g.settings, ) if form.validate_on_submit(): magicLink = dbMakeGalleryTicket( db=db, ticketName=form.name.data, validityHours=transformIfNotEmpty( form.validityhours.data, int ), multiplicity=transformIfNotEmpty( form.multiplicity.data, int ), ticketMessage=transformIfNotEmpty( form.ticketmessage.data ), box=box, boxPath=boxPath, user=user, urlRoot=request.url_root, settings=g.settings, ) flashMessage( 'Success', 'Done', ('Gallery ticket generated. Give the recipient ' 'the following magic link:'), pillText=magicLink, ) return redirect(url_for( 'lsView', lsPathString='/'.join(boxPath[1:]), )) else: defaultName = 'Gallery-View-%s' % describeBoxName(box) form.name.data = applyDefault(form.name.data, defaultName) form.ticketmessage.data = applyDefault( form.ticketmessage.data, 'Please, look at this gallery', ) maxValidityHours = g.settings['behaviour']['behaviour_tickets'][ 'max_ticket_validityhours']['value'] form.validityhours.data = applyDefault( form.validityhours.data, str(maxValidityHours) if maxValidityHours is not None else '', ) maxMultiplicity = g.settings['behaviour']['behaviour_tickets'][ 'max_ticket_multiplicity']['value'] form.multiplicity.data = applyDefault( form.multiplicity.data, str(maxMultiplicity) if maxMultiplicity is not None else '', ) return render_template( 'fsticket.html', pageTitle='Create public gallery-view ticket', pageSubtitle=('Recipient(s) of the ticket will be able to view ' 'files (and not contained boxes) in "%s", without ' 'an account, as a gallery. If setting a number of ' 'accesses, keep in mind that every single-file view' ' counts toward the total accesses.') % ( describeBoxTitle(box) ), baseMultiplicityCaption='Number of granted accesses', user=user, form=form, iconUrl=makeSettingImageUrl(g, 'app_images', 'gallery_ticket'), showFileMode=False, breadCrumbs=pathBCrumbs, )
def uploadMultipleFilesView(fsPathString=''): """Upload-several-files (to a box) route.""" user = g.user form = UploadMultipleFilesForm() db = dbGetDatabase() boxPath = splitPathString(fsPathString) request._onErrorUrl = url_for( 'lsView', lsPathString='/'.join(boxPath[1:]), ) fileStorageDirectory = g.settings['system']['system_directories'][ 'fs_directory']['value'] parentBoxPath = boxPath parentBox = getBoxFromPath(db, parentBoxPath, user) # if form.validate_on_submit(): uploadedFiles = [ uf for uf in request.files.getlist('files') if uf.filename != '' ] # filesToUpload = [ { 'box_id': parentBox.box_id, 'name': secure_filename(uploadedFile.filename), 'description': form.filesdescription.data, 'date': datetime.datetime.now(), 'fileObject': uploadedFile, } for uploadedFile in uploadedFiles ] makeThumbnails = g.settings['behaviour']['behaviour_appearance'][ 'extract_thumbnails']['value'] savingResult = saveAndAnalyseFilesInBox( db=db, files=filesToUpload, parentBox=parentBox, user=user, fileStorageDirectory=fileStorageDirectory, thumbnailFormat='thumbnail' if makeThumbnails else None, ) flashMessage('Success', 'Info', savingResult) return redirect(url_for( 'lsView', lsPathString='/'.join(parentBoxPath[1:]), )) else: pathBCrumbs = makeBreadCrumbs( parentBoxPath, g, appendedItems=[{ 'kind': 'link', 'target': None, 'name': 'Multiple upload', }], ) return render_template( 'uploadmultiplefiles.html', form=form, user=user, breadCrumbs=pathBCrumbs, pageTitle='Upload new files', pageSubtitle='Upload new files to box "%s"' % ( describeBoxTitle(parentBox) ), iconUrl=makeSettingImageUrl(g, 'app_images', 'multiple_upload') )
def editTextFileView(fsPathString=''): """Edit-text-file route.""" user = g.user db = dbGetDatabase() lsPath = splitPathString(fsPathString) boxPath, fileName = lsPath[:-1], lsPath[-1] request._onErrorUrl = url_for( 'lsView', lsPathString='/'.join(boxPath[1:]), ) fileStorageDirectory = g.settings['system']['system_directories'][ 'fs_directory']['value'] parentBox = getBoxFromPath(db, boxPath, user) file = getFileFromParent(db, parentBox, fileName, user) if file is None: raise OstracionError('File not found.') else: fileActions = prepareFileActions( db, file, boxPath[1:] + [file.name], parentBox, user, discardedActions={'text_edit'}, ) fileInfo = prepareFileInfo(db, file) form = EditTextFileForm() form.textformat.choices = [ (mId, mDesc['title']) for mId, mDesc in sorted( textFileViewingModes.items(), key=lambda kv: kv[1]['index'], ) ] ## if form.validate_on_submit(): newcontents = form.filecontents.data filePath = fileIdToPath( file.file_id, fileStorageDirectory=fileStorageDirectory, ) with open(filePath, 'w') as openFile: openFile.write('%s' % newcontents) # file properties fileProperties = determineFileProperties(filePath) newFile = File(**file.asDict()) newFile.mime_type = fileProperties['file_mime_type'] newFile.type = fileProperties['file_type'] newFile.size = fileProperties['file_size'] newFile.textual_mode = form.textformat.data newFile.editor_username = user.username updateFile(db, boxPath, file.name, newFile, user) # flashMessage('Info', 'Info', 'File "%s" saved.' % file.name) return redirect( url_for( 'lsView', lsPathString='/'.join(boxPath[1:]), ) ) else: form.filecontents.data = applyDefault( form.filecontents.data, open( fileIdToPath( file.file_id, fileStorageDirectory=fileStorageDirectory, ) ).read(), ) form.textformat.data = applyDefault( form.textformat.data, file.textual_mode, additionalNulls=['None'], ) pathBCrumbs = makeBreadCrumbs( boxPath, g, appendedItems=[ { 'kind': 'file', 'target': file, }, { 'kind': 'link', 'target': None, 'name': 'Edit text', } ], ) return render_template( 'edittextfile.html', user=user, form=form, fileActions=fileActions, fileInfo=fileInfo, pageTitle='"%s" file edit' % file.name, pageSubtitle='Click "Save" to commit the changes', breadCrumbs=pathBCrumbs, )
def loginView(): """Login view.""" user = g.user db = dbGetDatabase() g._onErrorUrl = url_for('loginView') if user is not None and user.is_authenticated: return redirect(url_for('indexView')) form = LoginForm() loginProtectionSeconds = int(g.settings['behaviour']['behaviour_security'] ['login_protection_seconds']['value']) if form.validate_on_submit(): # loginWaitSeconds = secondsToWaitBeforeLogin( db, request.remote_addr, doWrite=True, loginProtectionSeconds=loginProtectionSeconds, hashSalt=g.settings['behaviour']['behaviour_security'] ['ip_addr_hashing_salt']['value'], ) if loginWaitSeconds <= 0: # qUser = dbGetUser(db, form.username.data) if qUser and qUser.checkPassword(form.password.data): # newUser = User(**qUser.asDict()) newUser.last_login = datetime.datetime.now() dbUpdateUser(db, newUser, qUser) # login_user(qUser) # flashMessage( 'Success', 'Login successful', 'welcome, %s!' % (qUser.fullname, ), ) # return redirect(url_for('indexView')) else: g._onErrorUrl = url_for( 'loginView', username=form.username.data, ) raise OstracionError('invalid username or password') else: raise OstracionWarning( ('Automated repeated login protection. ' 'Please wait %i seconds ...') % int(loginWaitSeconds)) else: requestParams = request.args form.username.data = applyDefault( form.username.data, requestParams.get('username', ''), ) appShortName = g.settings['behaviour']['behaviour_appearance'][ 'application_short_name']['value'] iconUrl = makeSettingImageUrl(g, 'user_images', 'login') return render_template( 'login.html', form=form, user=user, pageTitle=loginTitle(g), pageSubtitle=loginSubtitle(g), iconUrl=iconUrl, )
def fsGalleryView(fsPathString=''): """ Gallery view route on a box. If just box, redirect to first file. If a file, use the ls of files to prepare prev/next links. That is to say, if user has read permission on box, gallery URLs are not index-based, rather filename-based and recalculated on actual 'ls'. """ isTopAccess = 1 if safeInt(request.args.get('top'), 0) != 0 else 0 user = g.user fileStorageDirectory = g.settings['system']['system_directories'][ 'fs_directory']['value'] db = dbGetDatabase() lsPath = splitPathString(fsPathString) # is it a box or a file? targetBox = getBoxFromPath(db, lsPath, user) if targetBox is not None: # if there are files, we redirect to the first one # otherwise, a message and falling back to ls view of the box files = sorted( getFilesFromBox(db, targetBox), key=lambda f: (f.name.lower(), f.name), ) if len(files) > 0: return redirect( url_for( 'fsGalleryView', fsPathString='/'.join(lsPath[1:] + [files[0].name]), top=str(isTopAccess), )) else: flashMessage( 'Info', 'Empty box', 'Cannot view as gallery a box without files', ) return redirect( url_for( 'lsView', lsPathString='/'.join(lsPath[1:]), )) else: # is it a file? boxPath, fileName = lsPath[:-1], lsPath[-1] parentBox = getBoxFromPath(db, boxPath, user) file = getFileFromParent(db, parentBox, fileName, user) if file is not None: if isTopAccess: # gallery navigation calculations files = sorted( getFilesFromBox(db, parentBox), key=lambda f: (f.name.lower(), f.name), ) thisFileIndex = [ idx for idx, fil in enumerate(files) if fil.name == fileName ][0] numGalleryFiles = len(files) nextFileIndex = (thisFileIndex + 1) % numGalleryFiles prevFileIndex = (thisFileIndex - 1 + numGalleryFiles) % numGalleryFiles # pathBCrumbs = makeBreadCrumbs( boxPath, g, appendedItems=[{ 'kind': 'link', 'name': 'Gallery view %i/%i' % ( thisFileIndex + 1, numGalleryFiles, ), 'target': None, }], ) fileContents = produceFileViewContents( db, file, mode='fsview', viewParameters={ 'boxPath': boxPath, 'fileName': fileName, }, fileStorageDirectory=fileStorageDirectory, ) fileActions = { 'gallery_prev': url_for( 'fsGalleryView', fsPathString='/'.join(boxPath[1:] + [files[prevFileIndex].name]), top=str(isTopAccess), ), 'gallery_next': url_for( 'fsGalleryView', fsPathString='/'.join(boxPath[1:] + [files[nextFileIndex].name]), top=str(isTopAccess), ), 'gallery_up': url_for( 'lsView', lsPathString='/'.join(boxPath[1:]), ), 'download': url_for( 'fsDownloadView', fsPathString=fsPathString, ), } return render_template( 'fileview.html', fileActions=fileActions, fileInfo=None, filecontents=fileContents, breadCrumbs=pathBCrumbs, user=user, pageTitle='"%s" gallery, file "%s" (%i/%i)' % ( parentBox.title, file.name, thisFileIndex + 1, numGalleryFiles, ), pageSubtitle=file.description, downloadUrl=None, hideBreadCrumbs=True, hideNavbar=True, ) else: return fsDownloadView(fsPathString=fsPathString) else: # extreme fallback to ls, which will hiearchically # deal with the retrieval return redirect(url_for('lsView', lsPathString=fsPathString))
def ticketGalleryView(ticketId, securityCode, page=0): """Actual route to view a gallery with a ticket.""" user = g.user db = dbGetDatabase() # richTicket = dbGetEnrichAndCheckTicket( db, 'g', ticketId, securityCode, request.url_root, ) if richTicket is None: raise OstracionError('Invalid ticket magic link') else: issuer = dbGetUser(db, richTicket['ticket'].username) if richTicket['redeemable']: # valid ticket. Further checks are on the way. noBandUsrTickets = g.settings['behaviour']['behaviour_tickets'][ 'protect_banned_user_tickets']['value'] if (not noBandUsrTickets or issuer.banned == 0): # boxPath = richTicket['metadata']['box_path'] request._onErrorUrl = url_for( 'lsView', lsPathString='/'.join(boxPath[1:]), ) parentBox = getBoxFromPath(db, boxPath[1:], issuer) if parentBox is not None: if parentBox.box_id == richTicket['metadata']['box_id']: galleryMessage = richTicket['metadata'].get('message') fileStorageDirectory = g.settings['system'][ 'system_directories']['fs_directory']['value'] files = sorted( getFilesFromBox(db, parentBox), key=lambda f: (f.name.lower(), f.name), ) numGalleryFiles = len(files) if numGalleryFiles > 0: thisFileIndex = page % numGalleryFiles file = files[thisFileIndex] # gallery navigation calculations nextFileIndex = (thisFileIndex + 1) % numGalleryFiles prevFileIndex = (thisFileIndex - 1 + numGalleryFiles) % numGalleryFiles # fileContents = produceFileViewContents( db, file, mode='galleryview', viewParameters={ 'boxPath': boxPath, 'fileName': file.name, 'ticketId': ticketId, 'securityCode': securityCode, }, fileStorageDirectory=fileStorageDirectory, urlRoot=request.url_root, protectBannedUserTickets=noBandUsrTickets, ) fileActions = { 'gallery_prev': url_for( 'ticketGalleryView', ticketId=ticketId, securityCode=securityCode, page=prevFileIndex, ), 'gallery_next': url_for( 'ticketGalleryView', ticketId=ticketId, securityCode=securityCode, page=nextFileIndex, ), 'homepage': url_for('lsView', ) } return render_template( 'fileview.html', fileActions=fileActions, fileInfo=None, filecontents=fileContents, breadCrumbs=None, user=user, pageTitle='Gallery view, file "%s" (%i/%i)' % ( file.name, thisFileIndex + 1, numGalleryFiles, ), pageSubtitle=(None if galleryMessage is None else ('Message on ticket: "%s"' % (galleryMessage, ))), downloadUrl=None, hideBreadCrumbs=True, hideNavbar=True, ) # return fsView(fsPathString) else: flashMessage( 'Info', 'Empty box', 'Cannot view as gallery a box without files', ) return redirect(url_for('lsView')) else: raise OstracionError('Ticket cannot be redeemed') else: raise OstracionError('Invalid ticket magic link') else: raise OstracionError('Ticket cannot be redeemed') else: raise OstracionError('Invalid ticket magic link')
def calendarMakerGenerateCalendar(): """ Calendar actual generation view. Handles everything: building temporary image files driving the engine functions to make the pdf inserting the pdf in the box removing temporary files """ user = g.user db = dbGetDatabase() request._onErrorUrl = url_for('calendarMakerIndexView', ) # destBoxString = request.cookies.get('apps_calendarmaker_destbox') currentCalendar = cookiesToCurrentCalendar(request.cookies) coverImagePathString = currentCalendar.get('cover_image_path_string') calendarImagePaths = currentCalendar.get('image_path_strings', []) cProps = currentCalendar.get('properties', {}) # if destBoxString is None: destBox = None else: destBoxPath = splitPathString(destBoxString) destBox = getBoxFromPath(db, destBoxPath, user) if coverImagePathString is None: coverImageFileObject = None else: coverImageFileObject = pathToFileStructure( db, user, coverImagePathString, ) # calendarImages = [ pathToFileStructure(db, user, imgPath) for imgPath in calendarImagePaths ] numRequiredImages = countMonths( cProps.get('year0'), cProps.get('month0'), cProps.get('year1'), cProps.get('month1'), ) if (destBox is None or coverImageFileObject is None or any([ci is None for ci in calendarImages]) or numRequiredImages is None or numRequiredImages > len(calendarImages)): raise OstracionError('Cannot generate calendar') else: fileStorageDirectory = g.settings['system']['system_directories'][ 'fs_directory']['value'] # proceed with generation tempFileDirectory = g.settings['system']['system_directories'][ 'temp_directory']['value'] mkDirP(tempFileDirectory) texImageCoverPath = duplicateImageForCalendar( fileIdToPath( coverImageFileObject['file'].file_id, fileStorageDirectory=fileStorageDirectory, ), os.path.join( tempFileDirectory, '%s.%s' % ( uuid4().hex, admittedImageMimeTypeToExtension[ coverImageFileObject['file'].mime_type], )), ) texImagePaths = [ duplicateImageForCalendar( fileIdToPath( imageFile['file'].file_id, fileStorageDirectory=fileStorageDirectory, ), os.path.join( tempFileDirectory, '%s.%s' % ( uuid4().hex, admittedImageMimeTypeToExtension[ imageFile['file'].mime_type], )), ) for imageFile in calendarImages ] # fsDeletionQueue = [texImageCoverPath] + texImagePaths createdPdfTitle = uuid4().hex createdFile, creationToDelete = makeCalendarPdf( cProps, texImagePaths, texImageCoverPath, tempFileDirectory, createdPdfTitle, ) # if createdFile is not None: # name and description calDescription = 'Calendar %i/%i - %i/%i' % ( cProps['month0'], cProps['year0'], cProps['month1'], cProps['year1'], ) calFileName = findFirstAvailableObjectNameInBox( db, destBox, 'calendar_', '.pdf', ) # place the pdf in the box placeFSFileInBox( db, user, fileStorageDirectory, destBox, createdFile, calFileName, calDescription, ) # flushing the delete queue flushFsDeleteQueue(fsDeletionQueue + creationToDelete + [createdFile]) # messaging the user successMessage = ('Calendar generated. Please find the file ' '"%s" in the destination box.') % calFileName flashMessage('Success', 'Info', successMessage) # redirecting user to box return redirect(url_for('lsView', lsPathString=destBoxString)) else: # flushing the delete queue flushFsDeleteQueue( fsDeletionQueue + creationToDelete + [createdFile], ) # messaging the user flashMessage('Error', 'Error', 'Could not generate the calendar') return redirect(url_for('calendarMakerIndexView'))
def before_request(): """ Preparation of globally-accessible info for all views, handling of special post-install situations and of banned users and logged-in-only setups. """ db = dbGetDatabase() g.user = current_user g.settings = dbLoadAllSettings(db, g.user) g.applicationLogoUrl = makeSettingImageUrl( g, 'ostracion_images', 'navbar_logo', ) # g.canPerformSearch = isUserWithinPermissionCircle( db, g.user, g.settings['behaviour']['search']['search_access']['value'], ) g.quickFindForm = QuickFindForm() g.canShowTreeView = isUserWithinPermissionCircle( db, g.user, g.settings['behaviour']['search']['tree_view_access']['value'], ) # task lists - must happen after the above (which set access flags) g.availableApps = selectAvailableApps(db, g.user, g) g.availableInfoItems = selectAvailableInfoItems(db, g.user, g) g.availableTools = selectAvailableTools(db, g.user, g) # if g.user.is_authenticated: g.user.setRoles(list(dbGetUserRoles(db, g.user))) if g.user.banned: flashMessage( 'Warning', 'Warning', ('Your user is momentarily banned. ' 'Please continue as anonymous'), ) logout_user() # handling of first-time post-install forced steps dirsSetUp = g.settings['managed']['post_install_finalisations'][ 'directories_set_up']['value'] if (not dirsSetUp and request.endpoint not in endpointsWithoutAppInitRerouting): if g.user.is_authenticated and userIsAdmin(db, g.user): flashMessage( 'Warning', 'Action required', ('Please review carefully the following directory settings ' 'to make the application function. Changing them later, ' 'when material is uploaded, is a disruptive operation.'), ) return redirect( url_for('adminHomeSettingsGenericView', settingType='system_settings')) else: if g.user.is_authenticated: logout_user() flashMessage( 'Warning', 'Action required', ('To finalise installation/make the application function ' 'properly, an administrator should log in and ' 'complete the setup'), ) return redirect(url_for('loginView')) else: # we care about reviewing the settings only if the dirs are OK if g.user.is_authenticated and userIsAdmin(db, g.user): if request.endpoint not in endpointsWithoutAppInitRerouting: settingsReviewed = g.settings['managed'][ 'post_install_finalisations']['settings_reviewed']['value'] if not settingsReviewed: flashMessage( 'Warning', 'Action required', ('Please carefully review the following application ' 'behaviour settings before opening the service ' 'to users (anyway, these settings can be ' 'changed at any time).'), ) return redirect( url_for('adminHomeSettingsGenericView', settingType='behaviour')) # we take care of terms-and-conditions acceptance here if request.endpoint not in endpointsWithoutTermAcceptanceBlocking: usersMustAbideByTOS = g.settings['terms']['terms']['terms_must_agree'][ 'value'] anonymousMustAbideByTOS = g.settings['terms']['terms'][ 'terms_must_agree_anonymous']['value'] currentTermVersion = g.settings['terms']['terms']['terms_version'][ 'value'] # if g.user.is_authenticated: mustAbideByTOS = usersMustAbideByTOS typeOfUsers = 'Users' storedTermsVersion = g.user.terms_accepted_version storedTermsAcceptance = safeInt( g.user.terms_accepted, default=None, ) else: mustAbideByTOS = anonymousMustAbideByTOS typeOfUsers = 'Visitors' storedTermsVersion = request.cookies.get('termsAcceptedVersion') storedTermsAcceptance = safeInt( request.cookies.get('termsAccepted'), default=None, ) # if storedTermsVersion == currentTermVersion: thisVersionAcceptance = storedTermsAcceptance else: thisVersionAcceptance = None # if thisVersionAcceptance is None: canContinueTermwise = not mustAbideByTOS elif thisVersionAcceptance == 0: # explicit not-acceptance canContinueTermwise = False else: # positive updated acceptance canContinueTermwise = True # if not canContinueTermwise: quotedTravelToPath = urllib.parse.quote_plus(request.full_path) flashMessage( 'Info', 'Action required', ('%s must review the Terms and Conditions' ' before accessing the site.') % typeOfUsers, ) return redirect( url_for( 'termsView', showAgreementButtons='y', travelTo=quotedTravelToPath, )) # here the ordinary flow is resumed if g.settings['behaviour']['behaviour_permissions'][ 'logged_in_users_only']['value']: if (not g.user.is_authenticated and request.endpoint not in endpointsWithoutInviteOnlyBlocking): return redirect(url_for('loginView'))